Skip to content

Commit a187f90

Browse files
authored
Merge pull request #363 from sbs2001/remove_advisory_hash_method
Refactor codebase and tests to treat Advisory class mutable
2 parents f262ef1 + 0e0fc2b commit a187f90

34 files changed

Lines changed: 1703 additions & 1390 deletions

vulnerabilities/data_source.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@
4747
logger = logging.getLogger(__name__)
4848

4949

50-
@dataclasses.dataclass
50+
@dataclasses.dataclass(order=True)
5151
class VulnerabilitySeverity:
5252
system: ScoringSystem
5353
value: str
5454

5555

56-
@dataclasses.dataclass
56+
@dataclasses.dataclass(order=True)
5757
class Reference:
5858

5959
reference_id: str = ""
@@ -64,8 +64,16 @@ def __post_init__(self):
6464
if not any([self.url, self.reference_id]):
6565
raise TypeError
6666

67+
def normalized(self):
68+
severities = sorted(self.severities)
69+
return Reference(
70+
reference_id=self.reference_id,
71+
url=self.url,
72+
severities=severities
73+
)
6774

68-
@dataclasses.dataclass
75+
76+
@dataclasses.dataclass(order=True)
6977
class Advisory:
7078
"""
7179
This data class expresses the contract between data sources and the import runner.
@@ -78,19 +86,27 @@ class Advisory:
7886
"""
7987

8088
summary: str
81-
impacted_package_urls: Iterable[PackageURL]
89+
vulnerability_id: Optional[str] = None
90+
impacted_package_urls: Iterable[PackageURL] = dataclasses.field(default_factory=list)
8291
resolved_package_urls: Iterable[PackageURL] = dataclasses.field(default_factory=list)
8392
vuln_references: List[Reference] = dataclasses.field(default_factory=list)
84-
vulnerability_id: Optional[str] = None
8593

86-
def __hash__(self):
87-
s = "{}{}{}{}".format(
88-
self.summary,
89-
''.join(sorted([str(p) for p in self.impacted_package_urls])),
90-
''.join(sorted([str(p) for p in self.resolved_package_urls])),
91-
self.vulnerability_id,
94+
def normalized(self):
95+
impacted_package_urls = {package_url for package_url in self.impacted_package_urls}
96+
resolved_package_urls = {package_url for package_url in self.resolved_package_urls}
97+
vuln_references = sorted(
98+
self.vuln_references, key=lambda reference: (reference.reference_id, reference.url)
99+
)
100+
for index, _ in enumerate(self.vuln_references):
101+
vuln_references[index] = (vuln_references[index].normalized())
102+
103+
return Advisory(
104+
summary=self.summary,
105+
vulnerability_id=self.vulnerability_id,
106+
impacted_package_urls=impacted_package_urls,
107+
resolved_package_urls=resolved_package_urls,
108+
vuln_references=vuln_references,
92109
)
93-
return hash(s)
94110

95111

96112
class InvalidConfigurationError(Exception):
@@ -205,11 +221,15 @@ def batch_advisories(self, advisories: List[Advisory]) -> Set[Advisory]:
205221
"""
206222
Yield batches of the passed in list of advisories.
207223
"""
208-
advisories = advisories[:] # copy the list as we are mutating it in the loop below
224+
225+
# TODO make this less cryptic and efficient
226+
227+
advisories = advisories[:]
228+
# copy the list as we are mutating it in the loop below
209229

210230
while advisories:
211231
b, advisories = advisories[: self.batch_size], advisories[self.batch_size:]
212-
yield set(b)
232+
yield b
213233

214234

215235
@dataclasses.dataclass

vulnerabilities/importers/alpine_linux.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,14 @@ def _load_advisories(
181181
)
182182
)
183183

184+
# TODO: Handle the CVE-????-????? case
184185
advisories.append(
185186
Advisory(
186187
summary="",
187188
impacted_package_urls=[],
188189
resolved_package_urls=resolved_purls,
189190
vuln_references=references,
190-
vulnerability_id=vuln_ids[0] if vuln_ids[0] != "CVE-????-?????" else None,
191+
vulnerability_id=vuln_ids[0] if vuln_ids[0] != "CVE-????-?????" else "",
191192
)
192193
)
193194

vulnerabilities/importers/rust.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ def _load_advisories(self, files) -> Set[Advisory]:
7575

7676
while files:
7777
batch, files = files[: self.batch_size], files[self.batch_size:]
78-
advisories = set()
78+
advisories = []
7979
for path in batch:
8080
advisory = self._load_advisory(path)
8181
if advisory:
82-
advisories.add(advisory)
82+
advisories.append(advisory)
8383
yield advisories
8484

8585
def collect_packages(self, paths):

vulnerabilities/severity_systems.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dataclasses
22

33

4-
@dataclasses.dataclass
4+
@dataclasses.dataclass(order=True)
55
class ScoringSystem:
66

77
# a short identifier for the scoring system.

vulnerabilities/tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828

2929
@pytest.fixture
3030
def no_mkdir(monkeypatch):
31-
monkeypatch.delattr('os.mkdir')
31+
monkeypatch.delattr("os.mkdir")
3232

3333

3434
@pytest.fixture
3535
def no_rmtree(monkeypatch):
36-
monkeypatch.delattr('shutil.rmtree')
36+
monkeypatch.delattr("shutil.rmtree")

vulnerabilities/tests/test_alpine.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def test__process_link(self):
141141
url="https://xenbits.xen.org/xsa/advisory-295.html", reference_id="XSA-295"
142142
)
143143
],
144-
vulnerability_id=None,
144+
vulnerability_id="",
145145
),
146146
]
147147
mock_requests = MagicMock()
@@ -151,4 +151,7 @@ def test__process_link(self):
151151
mock_content.content = f
152152
with patch("vulnerabilities.importers.alpine_linux.requests", new=mock_requests):
153153
found_advisories = self.data_source._process_link("does not matter")
154-
assert expected_advisories == found_advisories
154+
155+
found_advisories = list(map(Advisory.normalized, found_advisories))
156+
expected_advisories = list(map(Advisory.normalized, expected_advisories))
157+
assert sorted(found_advisories) == sorted(expected_advisories)

vulnerabilities/tests/test_apache_kafka.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,8 @@ def test_to_version_ranges(self):
5656

5757
def test_to_advisory(self):
5858
data_source = ApacheKafkaDataSource(batch_size=1)
59-
data_source.version_api = GitHubTagsAPI(
60-
cache={"apache/kafka": ["2.1.2", "0.10.2.2"]}
61-
)
62-
expected_data = [
59+
data_source.version_api = GitHubTagsAPI(cache={"apache/kafka": ["2.1.2", "0.10.2.2"]})
60+
expected_advisories = [
6361
Advisory(
6462
summary="In Apache Kafka versions between 0.11.0.0 and 2.1.0, it is possible to "
6563
"manually\n craft a Produce request which bypasses transaction/idempotent ACL "
@@ -97,6 +95,8 @@ def test_to_advisory(self):
9795
)
9896
]
9997
with open(TEST_DATA) as f:
100-
found_data = data_source.to_advisory(f)
98+
found_advisories = data_source.to_advisory(f)
10199

102-
assert found_data == expected_data
100+
found_advisories = list(map(Advisory.normalized, found_advisories))
101+
expected_advisories = list(map(Advisory.normalized, expected_advisories))
102+
assert sorted(found_advisories) == sorted(expected_advisories)

0 commit comments

Comments
 (0)