Skip to content

Commit 92eb01a

Browse files
authored
Merge pull request #935 from nexB/597-migrate-archlinux
Migrate archlinux importer
2 parents 6bad931 + 1cadb72 commit 92eb01a

File tree

11 files changed

+346
-134
lines changed

11 files changed

+346
-134
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ toml==0.10.2
106106
tomli==2.0.1
107107
traitlets==5.1.1
108108
typing_extensions==4.1.1
109-
univers==30.7.0
109+
univers==30.9.0
110110
urllib3==1.26.9
111111
wcwidth==0.2.5
112112
websocket-client==0.59.0

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ install_requires =
6565

6666
#essentials
6767
packageurl-python>=0.9.4
68-
univers>=30.3.1
68+
univers>=30.9.0
6969
license-expression>=21.6.14
7070

7171
# file and data formats

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#
99

1010
from vulnerabilities.importers import alpine_linux
11+
from vulnerabilities.importers import archlinux
1112
from vulnerabilities.importers import debian
1213
from vulnerabilities.importers import github
1314
from vulnerabilities.importers import gitlab
@@ -29,6 +30,7 @@
2930
debian.DebianImporter,
3031
gitlab.GitLabAPIImporter,
3132
pypa.PyPaImporter,
33+
archlinux.ArchlinuxImporter,
3234
]
3335

3436
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/alpine_linux.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from vulnerabilities.references import WireSharkReference
3232
from vulnerabilities.references import XsaReference
3333
from vulnerabilities.references import ZbxReference
34+
from vulnerabilities.utils import fetch_response
3435
from vulnerabilities.utils import is_cve
3536

3637
LOGGER = logging.getLogger(__name__)
@@ -58,16 +59,6 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
5859
yield from process_record(record)
5960

6061

61-
def fetch_response(url):
62-
"""
63-
Fetch and return `response` from the `url`
64-
"""
65-
response = requests.get(url)
66-
if response.status_code == 200:
67-
return response
68-
raise Exception(f"Failed to fetch data from {url!r} with status code: {response.status_code!r}")
69-
70-
7162
def fetch_advisory_directory_links(page_response_content: str) -> List[str]:
7263
"""
7364
Return a list of advisory directory links present in `page_response_content` html string

vulnerabilities/importers/archlinux.py

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,64 @@
66
# See https://github.com/nexB/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9-
import dataclasses
10-
import json
9+
1110
from typing import Iterable
1211
from typing import List
1312
from typing import Mapping
14-
from typing import Set
1513
from urllib.request import urlopen
1614

1715
from packageurl import PackageURL
16+
from univers.version_range import ArchLinuxVersionRange
17+
from univers.versions import ArchLinuxVersion
1818

1919
from vulnerabilities import severity_systems
2020
from vulnerabilities.importer import AdvisoryData
21+
from vulnerabilities.importer import AffectedPackage
2122
from vulnerabilities.importer import Importer
2223
from vulnerabilities.importer import Reference
2324
from vulnerabilities.importer import VulnerabilitySeverity
24-
from vulnerabilities.utils import nearest_patched_package
25+
from vulnerabilities.utils import fetch_response
2526

2627

2728
class ArchlinuxImporter(Importer):
28-
def __enter__(self):
29-
self._api_response = self._fetch()
30-
31-
def updated_advisories(self) -> Set[AdvisoryData]:
32-
advisories = []
29+
url = "https://security.archlinux.org/json"
30+
spdx_license_expression = "MIT"
31+
license_url = "https://github.com/archlinux/arch-security-tracker/blob/master/LICENSE"
3332

34-
for record in self._api_response:
35-
advisories.extend(self._parse(record))
33+
def fetch(self) -> Iterable[Mapping]:
34+
response = fetch_response(self.url)
35+
return response.json()
3636

37-
return self.batch_advisories(advisories)
37+
def advisory_data(self) -> Iterable[AdvisoryData]:
38+
for record in self.fetch():
39+
yield from self.parse_advisory(record)
3840

39-
def _fetch(self) -> Iterable[Mapping]:
40-
with urlopen(self.config.archlinux_tracker_url) as response:
41-
return json.load(response)
42-
43-
def _parse(self, record) -> List[AdvisoryData]:
41+
def parse_advisory(self, record) -> List[AdvisoryData]:
4442
advisories = []
45-
46-
for cve_id in record["issues"]:
43+
aliases = record.get("issues") or []
44+
for alias in aliases:
4745
affected_packages = []
4846
for name in record["packages"]:
49-
impacted_purls, resolved_purls = [], []
50-
impacted_purls.append(
51-
PackageURL(
47+
summary = record.get("type") or ""
48+
if summary == "unknown":
49+
summary = ""
50+
affected = record.get("affected") or ""
51+
affected_version_range = (
52+
ArchLinuxVersionRange.from_versions([affected]) if affected else None
53+
)
54+
fixed = record.get("fixed") or ""
55+
fixed_version = ArchLinuxVersion(fixed) if fixed else None
56+
affected_packages = []
57+
affected_package = AffectedPackage(
58+
package=PackageURL(
5259
name=name,
53-
type="pacman",
60+
type="alpm",
5461
namespace="archlinux",
55-
version=record["affected"],
56-
)
62+
),
63+
affected_version_range=affected_version_range,
64+
fixed_version=fixed_version,
5765
)
58-
59-
if record["fixed"]:
60-
resolved_purls.append(
61-
PackageURL(
62-
name=name,
63-
type="pacman",
64-
namespace="archlinux",
65-
version=record["fixed"],
66-
)
67-
)
68-
affected_packages.extend(nearest_patched_package(impacted_purls, resolved_purls))
66+
affected_packages.append(affected_package)
6967

7068
references = []
7169
references.append(
@@ -89,9 +87,9 @@ def _parse(self, record) -> List[AdvisoryData]:
8987
)
9088

9189
advisories.append(
92-
Advisory(
93-
vulnerability_id=cve_id,
94-
summary="",
90+
AdvisoryData(
91+
aliases=[alias],
92+
summary=summary,
9593
affected_packages=affected_packages,
9694
references=references,
9795
)

vulnerabilities/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def no_rmtree(monkeypatch):
2929
"test_apache_kafka.py",
3030
"test_apache_tomcat.py",
3131
"test_api.py",
32-
"test_archlinux.py",
3332
"test_debian_oval.py",
3433
"test_elixir_security.py",
3534
"test_gentoo.py",

vulnerabilities/tests/test_archlinux.py

Lines changed: 33 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -16,87 +16,38 @@
1616

1717
from vulnerabilities import models
1818
from vulnerabilities.import_runner import ImportRunner
19+
from vulnerabilities.importers import archlinux
20+
from vulnerabilities.tests import util_tests
1921

2022
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21-
TEST_DATA = os.path.join(BASE_DIR, "test_data/")
22-
23-
24-
class ArchlinuxImportTest(TestCase):
25-
@classmethod
26-
def setUpClass(cls) -> None:
27-
fixture_path = os.path.join(TEST_DATA, "archlinux.json")
28-
with open(fixture_path) as f:
29-
cls.mock_response = json.load(f)
30-
31-
cls.importer = models.Importer.objects.create(
32-
name="archlinux_unittests",
33-
license="",
34-
last_run=None,
35-
data_source="ArchlinuxImporter",
36-
data_source_cfg={
37-
"archlinux_tracker_url": "https://security.example.com/json",
38-
},
39-
)
40-
41-
@classmethod
42-
def tearDownClass(cls) -> None:
43-
pass
44-
45-
def test_import(self):
46-
runner = ImportRunner(self.importer, 5)
47-
48-
with patch(
49-
"vulnerabilities.importers.ArchlinuxImporter._fetch", return_value=self.mock_response
50-
):
51-
runner.run()
52-
assert models.Vulnerability.objects.count() == 6
53-
assert models.VulnerabilityReference.objects.count() == 10
54-
assert models.PackageRelatedVulnerability.objects.all().count() == 12
55-
assert (
56-
models.PackageRelatedVulnerability.objects.filter(patched_package__isnull=False).count()
57-
== 8
58-
)
59-
assert models.Package.objects.count() == 10
60-
61-
self.assert_for_package(
62-
"squid",
63-
"4.10-2",
64-
cve_ids={"CVE-2020-11945", "CVE-2019-12521", "CVE-2019-12519"},
65-
)
66-
self.assert_for_package("openconnect", "1:8.05-1", cve_ids={"CVE-2020-12823"})
67-
self.assert_for_package(
68-
"wireshark-common",
69-
"2.6.0-1",
70-
cve_ids={"CVE-2018-11362", "CVE-2018-11361"},
71-
)
72-
self.assert_for_package(
73-
"wireshark-gtk",
74-
"2.6.0-1",
75-
cve_ids={"CVE-2018-11362", "CVE-2018-11361"},
76-
)
77-
self.assert_for_package(
78-
"wireshark-cli",
79-
"2.6.0-1",
80-
cve_ids={"CVE-2018-11362", "CVE-2018-11361"},
81-
)
82-
self.assert_for_package(
83-
"wireshark-qt",
84-
"2.6.0-1",
85-
cve_ids={"CVE-2018-11362", "CVE-2018-11361"},
86-
)
87-
self.assert_for_package("wireshark-common", "2.6.1-1")
88-
self.assert_for_package("wireshark-gtk", "2.6.1-1")
89-
self.assert_for_package("wireshark-cli", "2.6.1-1")
90-
self.assert_for_package("wireshark-qt", "2.6.1-1")
91-
92-
def assert_for_package(self, name, version, cve_ids=None):
93-
qs = models.Package.objects.filter(
94-
name=name,
95-
version=version,
96-
type="pacman",
97-
namespace="archlinux",
98-
)
99-
assert qs
100-
101-
if cve_ids:
102-
assert cve_ids == {v.vulnerability_id for v in qs[0].vulnerabilities.all()}
23+
TEST_DATA = os.path.join(BASE_DIR, "test_data/archlinux")
24+
25+
26+
def test_parse_advisory_single():
27+
record = {
28+
"name": "AVG-2781",
29+
"packages": ["python-pyjwt"],
30+
"status": "Unknown",
31+
"severity": "Unknown",
32+
"type": "unknown",
33+
"affected": "2.3.0-1",
34+
"fixed": "2.4.0-1",
35+
"ticket": None,
36+
"issues": ["CVE-2022-29217"],
37+
"advisories": [],
38+
}
39+
40+
advisory_data = archlinux.ArchlinuxImporter().parse_advisory(record)
41+
result = [data.to_dict() for data in advisory_data]
42+
expected_file = os.path.join(TEST_DATA, f"parse-advisory-archlinux-expected.json")
43+
util_tests.check_results_against_json(result, expected_file)
44+
45+
46+
@patch("vulnerabilities.importers.archlinux.ArchlinuxImporter.fetch")
47+
def test_archlinux_importer(mock_response):
48+
with open(os.path.join(TEST_DATA, "archlinux-multi.json")) as f:
49+
mock_response.return_value = json.load(f)
50+
51+
expected_file = os.path.join(TEST_DATA, f"archlinux-multi-expected.json")
52+
result = [data.to_dict() for data in list(archlinux.ArchlinuxImporter().advisory_data())]
53+
util_tests.check_results_against_json(result, expected_file)

0 commit comments

Comments
 (0)