Skip to content

Commit f7a8fe0

Browse files
authored
Add CSAF tests (#37)
* CSAF diffing tests. Signed-off-by: Caroline Russell <[email protected]> * Bump version. Signed-off-by: Caroline Russell <[email protected]> --------- Signed-off-by: Caroline Russell <[email protected]>
1 parent 90abe40 commit f7a8fe0

File tree

9 files changed

+287
-9
lines changed

9 files changed

+287
-9
lines changed

custom_json_diff/cli.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def build_args() -> argparse.Namespace:
2424
preconfig_diff_type="",
2525
allow_new_versions=False,
2626
report_template="",
27-
components_only=False,
2827
exclude=[],
2928
allow_new_data=False,
3029
include=[]

custom_json_diff/lib/custom_diff.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,11 @@ def load_json(json_file: str, options: "Options") -> "BomDicts|CsafDicts|FlatDic
200200
logger.error("Invalid JSON: %s", json_file)
201201
sys.exit(1)
202202
if options.preconfig_type == "bom":
203-
data = sort_dict_lists(data, ["bom-ref", "cve", "id", "url", "text", "content", "ref", "name", "value"])
203+
data = sort_dict_lists(data, options.sort_keys)
204204
data = filter_dict(data, options).to_dict(unflat=True)
205205
return BomDicts(options, json_file, data)
206206
if options.preconfig_type == "csaf":
207-
data = sort_dict_lists(data, ["text", "url", "product_id"])
207+
data = sort_dict_lists(data, options.sort_keys)
208208
data = filter_dict(data, options).to_dict(unflat=True)
209209
return CsafDicts(options, json_file, data)
210210
return filter_dict(data, options)

custom_json_diff/lib/custom_diff_classes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,10 @@ def __eq__(self, other):
572572
def __ne__(self, other):
573573
return not self == other
574574

575+
def clear(self):
576+
options = self.options
577+
self.__init__(data={}, options=options)
578+
575579
def to_dict(self):
576580
return {
577581
"acknowledgements": self.acknowledgements,

custom_json_diff/lib/utils.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,7 @@ def compare_versions(v1: str|None, v2: str|None, comparator: str) -> bool:
9494
version_2: str|semver.Version|None = semver.Version.parse(v2)
9595
except ValueError:
9696
logger.debug("Could not parse one or more of these versions: %s, %s", v1, v2)
97-
version_1, version_2 = v1, v2
98-
except TypeError:
99-
logger.debug("Could not parse one or more of these versions: %s, %s", v1, v2)
100-
return False
97+
return manual_version_compare(v1, v2, comparator)
10198
return compare_generic(version_1, version_2, comparator) #type: ignore
10299

103100

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "custom-json-diff"
3-
version = "2.0.0-beta.1"
3+
version = "2.0.0"
44
description = "CycloneDx BOM and Oasis CSAF diffing and comparison tool."
55
authors = [
66
{ name = "Caroline Russell", email = "[email protected]" },

test/csaf_1.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

test/csaf_2.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

test/test_csaf_diff.py

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import json
2+
from copy import deepcopy
3+
4+
import pytest
5+
6+
from custom_json_diff.lib.custom_diff import compare_dicts, perform_bom_diff, perform_csaf_diff
7+
from custom_json_diff.lib.custom_diff_classes import (
8+
BomComponent, BomDicts, CsafDicts, CsafVulnerability, FlatDicts, Options, BomVdr, BomVdrAffects
9+
)
10+
11+
12+
@pytest.fixture
13+
def options_1():
14+
return Options(file_1="test/csaf_1.json", file_2="test/csaf_2.json", preconfig_type="csaf")
15+
16+
17+
@pytest.fixture
18+
def options_3():
19+
return Options(file_1="test/csaf_1.json", file_2="test/csaf_2.json", preconfig_type="csaf", allow_new_data=True)
20+
21+
22+
@pytest.fixture
23+
def csaf_dicts_1():
24+
options = Options(file_1="csaf_1.json", file_2="csaf_2.json", preconfig_type="csaf", allow_new_data=True)
25+
return CsafDicts(options, "csaf_1.json", vulnerabilities=[CsafVulnerability({
26+
"acknowledgements": [
27+
[
28+
{
29+
"organization": "NVD",
30+
"urls": [
31+
"https://nvd.nist.gov/vuln/detail/CVE-2024-39689"
32+
]
33+
}
34+
]
35+
],
36+
"cve": "CVE-2024-39689",
37+
"cwe": {
38+
"id": "345",
39+
"name": "Insufficient Verification of Data Authenticity"
40+
},
41+
"discovery_date": "2024-07-05T20:06:40",
42+
"ids": [
43+
{
44+
"system_name": "CVE Record",
45+
"text": "CVE-2024-39689"
46+
},
47+
{
48+
"system_name": "GitHub Advisory",
49+
"text": "GHSA-248v-346w-9cwc"
50+
}
51+
],
52+
"notes": [
53+
{
54+
"category": "description",
55+
"details": "Vulnerability Description",
56+
"text": "Certifi removes GLOBALTRUST root certificate"
57+
},
58+
{
59+
"category": "details",
60+
"details": "Vulnerability Details",
61+
"text": "# Certifi removes GLOBALTRUST root certificate Certifi 2024.07.04 removes root certificates from \"GLOBALTRUST\" from the root store. These are in the process of being removed from Mozilla's trust store. GLOBALTRUST's root certificates are being removed pursuant to an investigation which identified \"long-running and unresolved compliance issues\". Conclusions of Mozilla's investigation can be found [here]( https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/XpknYMPO8dI)."
62+
}
63+
],
64+
"product_status": {
65+
"known_affected": [
66+
"certifi@vers:pypi/>=2021.05.30|<2024.07.04"
67+
],
68+
"known_not_affected": [
69+
70+
]
71+
},
72+
"references": [
73+
{
74+
"summary": "GitHub Advisory GHSA-248v-346w-9cwc",
75+
"url": "https://github.com/certifi/python-certifi/security/advisories/GHSA-248v-346w-9cwc"
76+
},
77+
{
78+
"summary": "Google Mailing List",
79+
"url": "https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/XpknYMPO8dI"
80+
},
81+
{
82+
"summary": "CVE Record",
83+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-39689"
84+
}
85+
],
86+
"scores": [
87+
{
88+
"cvss_v3": {
89+
"attackComplexity": "HIGH",
90+
"attackVector": "NETWORK",
91+
"availabilityImpact": "NONE",
92+
"baseScore": 3.1,
93+
"baseSeverity": "LOW",
94+
"confidentialityImpact": "LOW",
95+
"environmentalScore": 3.1,
96+
"environmentalSeverity": "LOW",
97+
"integrityImpact": "NONE",
98+
"modifiedAttackComplexity": "HIGH",
99+
"modifiedAttackVector": "NETWORK",
100+
"modifiedAvailabilityImpact": "NONE",
101+
"modifiedConfidentialityImpact": "LOW",
102+
"modifiedIntegrityImpact": "NONE",
103+
"modifiedPrivilegesRequired": "NONE",
104+
"modifiedScope": "UNCHANGED",
105+
"modifiedUserInteraction": "REQUIRED",
106+
"privilegesRequired": "NONE",
107+
"scope": "UNCHANGED",
108+
"temporalScore": 3.1,
109+
"temporalSeverity": "LOW",
110+
"userInteraction": "REQUIRED",
111+
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N",
112+
"version": "3.1"
113+
},
114+
"products": [
115+
"certifi@vers:pypi/>=2021.05.30|<2024.07.04"
116+
]
117+
}
118+
],
119+
"title": "CVE-2024-39689/pkg:pypi/[email protected]"
120+
}, options)
121+
122+
])
123+
124+
125+
@pytest.fixture
126+
def results():
127+
with open("test/test_data.json", "r", encoding="utf-8") as f:
128+
return json.load(f)
129+
130+
131+
def test_csaf_diff(results, options_1):
132+
result, j1, j2 = compare_dicts(options_1)
133+
_, result_summary = perform_csaf_diff(j1, j2)
134+
assert result_summary == results["result_13"]
135+
result, j2, j1 = compare_dicts(options_1)
136+
_, result_summary = perform_csaf_diff(j2, j1)
137+
results["result_14"] = result_summary
138+
assert result_summary == results["result_14"]
139+
140+
141+
def test_csaf_diff_vuln_options(options_1):
142+
# test don't allow --allow-new-data or --allow-new-versions
143+
bom1 = BomVdr(id="CVE-2022-25881",options=options_1)
144+
bom2 = BomVdr(id="CVE-2022-25881",options=options_1)
145+
bom2.options.doc_num = 2
146+
assert bom1 == bom2
147+
bom2.id = "CVE-2022-25883"
148+
assert bom1 != bom2
149+
bom1.clear(), bom2.clear()
150+
151+
bom1.bom_ref, bom2.bom_ref = "NPM-1091792/pkg:npm/[email protected]", "NPM-1091792/pkg:npm/[email protected]"
152+
assert bom1 == bom2
153+
bom2.bom_ref = "NPM-1091792/pkg:npm/[email protected]"
154+
assert bom1 != bom2
155+
bom1.clear(), bom2.clear()
156+
157+
bom1.advisories = [{"url": "https://security.netapp.com/advisory/ntap-20230622-0008"}]
158+
bom2.advisories = [{"url": "https://security.netapp.com/advisory/ntap-20230622-0008"}]
159+
assert bom1 == bom2
160+
bom2.advisories = [{"url": "https://security.netapp.com/advisory/ntap-20230622-0009"}]
161+
assert bom1 != bom2
162+
bom1.clear(), bom2.clear()
163+
164+
bom1.affects = [BomVdrAffects({"ref": "pkg:npm/[email protected]", "versions": [{
165+
"range": "vers:npm/>=0.0.0|<=1.0.11", "status": "affected"}]}, options=bom1.options)]
166+
bom2.affects = [BomVdrAffects(data={"ref": "pkg:npm/[email protected]", "versions": [{
167+
"range": "vers:npm/>=0.0.0|<=1.0.11", "status": "affected"}]}, options=bom2.options)]
168+
assert bom1 == bom2
169+
bom2.affects = [BomVdrAffects(data={"ref": "pkg:npm/[email protected]", "versions": [{
170+
"range": "vers:npm/>=0.0.0|<=1.0.11", "status": "affected"}]}, options=bom2.options)]
171+
assert bom1 != bom2
172+
bom1.clear(), bom2.clear()
173+
174+
bom1.analysis = {"state": "exploitable", "detail": "See https://seclists.org/bugtraq/2019/May/68"}
175+
bom2.analysis = {"state": "exploitable", "detail": "See https://seclists.org/bugtraq/2019/May/68"}
176+
assert bom1 == bom2
177+
bom1.analysis = {}
178+
assert bom1 != bom2
179+
bom1.clear(), bom2.clear()
180+
181+
bom1.cwes = ["1333"]
182+
bom2.cwes = ["1333"]
183+
assert bom1 == bom2
184+
bom2.cwes = ["1333", "1334"]
185+
assert bom1 != bom2
186+
bom1.clear(), bom2.clear()
187+
188+
bom1.description = "lorem ipsum dolor sit amet"
189+
bom2.description = "lorem ipsum dolor sit amet"
190+
assert bom1 == bom2
191+
bom2.description = "lorem ipsum dolor"
192+
assert bom1 != bom2
193+
bom1.clear(), bom2.clear()
194+
195+
bom1.detail = "lorem ipsum dolor sit amet"
196+
bom2.detail = "lorem ipsum dolor sit amet"
197+
assert bom1 == bom2
198+
bom2.detail = "lorem ipsum dolor"
199+
assert bom1 != bom2
200+
bom1.clear(), bom2.clear()
201+
202+
bom1.properties = [{"name": "depscan:insights", "value": "Indirect dependency"}]
203+
bom2.properties = [{"name": "depscan:insights", "value": "Indirect dependency"}]
204+
assert bom1 == bom2
205+
bom2.properties = [{"name": "depscan:insights", "value": "Indirect dependency"}, {"name": "depscan:prioritized", "value": "false"}]
206+
assert bom1 != bom2
207+
bom1.clear(), bom2.clear()
208+
209+
bom1.published, bom2.published = "2020-09-01T20:42:44", "2020-09-01T20:42:44"
210+
assert bom1 == bom2
211+
bom2.published = "2021-09-01T20:42:44"
212+
assert bom1 != bom2
213+
bom1.clear(), bom2.clear()
214+
215+
bom1.ratings = [{"method": "CVSSv31", "severity": "MEDIUM", "score": 5.0, "vector": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L"}]
216+
bom2.ratings = [{"method": "CVSSv31", "severity": "MEDIUM", "score": 5.0, "vector": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L"}]
217+
assert bom1 == bom2
218+
bom2.ratings = [{"method": "CVSSv31", "severity": "MEDIUM", "score": 7.0, "vector": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L"}]
219+
assert bom1 != bom2
220+
bom1.clear(), bom2.clear()
221+
222+
bom1.recommendation, bom2.recommendation = "lorem ipsum dolor sit amet", "lorem ipsum dolor sit amet"
223+
assert bom1 == bom2
224+
bom2.recommendation = "lorem ipsum dolor"
225+
assert bom1 != bom2
226+
bom1.clear(), bom2.clear()
227+
228+
bom1.references = [{"id": "CVE-2022-23541", "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23541", "name": "NVD"}}]
229+
bom2.references = [{"id": "CVE-2022-23541", "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23541", "name": "NVD"}}]
230+
assert bom1 == bom2
231+
bom1.references.append({"id": "GHSA-hjrf-2m68-5959", "source": {"name": "GitHub Advisory", "url": "https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-hjrf-2m68-5959"}})
232+
assert bom1 != bom2
233+
bom1.clear(), bom2.clear()
234+
235+
bom1.source = {"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23541", "name": "NVD"}
236+
bom2.source = {"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23541", "name": "NVD"}
237+
assert bom1 == bom2
238+
bom2.source = {"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-23542", "name": "NVD"}
239+
assert bom1 != bom2
240+
bom1.clear(), bom2.clear()
241+
242+
bom1.updated, bom2.updated = "2020-09-01T20:42:44", "2020-09-01T20:42:44"
243+
assert bom1 == bom2
244+
bom2.updated = "2021-09-01T20:42:44"
245+
assert bom1 != bom2
246+
247+
248+
def test_csaf_diff_vuln_options_allow_new_data(options_3):
249+
# test --allow-new-data
250+
options_3_copy = deepcopy(options_3)
251+
options_3_copy.doc_num = 2
252+
csaf1, csaf2 = CsafVulnerability(data={"title": "CVE-2022-25881"},options=options_3), CsafVulnerability(data={"title": "CVE-2022-25881"},options=options_3_copy)
253+
assert csaf1 == csaf2
254+
csaf1.title, csaf2.title = "CVE-2022-25883", ""
255+
assert csaf1 != csaf2
256+
csaf1.clear(), csaf2.clear()
257+
258+
csaf1.acknowledgements = []
259+
csaf2.acknowledgements = [{"organization": "NVD", "urls": ["https://nvd.nist.gov/vuln/detail/CVE-2024-39689"]}]
260+
assert csaf1 == csaf2
261+
csaf1.acknowledgements, csaf2.acknowledgements = csaf2.acknowledgements, csaf1.acknowledgements
262+
assert csaf1 != csaf2
263+
csaf1.clear(), csaf2.clear()
264+
265+
csaf1.cwe = {"id": "345", "name": "Insufficient Verification of Data Authenticity"}
266+
csaf2.cwe = {"id": "345", "name": "Insufficient Verification of Data Authenticity"}
267+
assert csaf1 == csaf2
268+
csaf1.cwe["id"] = "500"
269+
assert csaf1 != csaf2
270+
csaf1.clear(), csaf2.clear()
271+
272+
csaf1.discovery_date, csaf2.discovery_date = "", "2020-09-01T20:42:44"
273+
assert csaf1 == csaf2
274+
csaf1.discovery_date, csaf2.discovery_date = csaf2.discovery_date, csaf1.discovery_date
275+
assert csaf1 != csaf2
276+
csaf1.clear(), csaf2.clear()

test/test_data.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)