Skip to content

Commit 3f17fca

Browse files
committed
Add Postgres Live V2 Importer Pipeline #1980
* Add Postgres Live V2 Importer * Add tests for the Postgres Live V2 Importer * Tested functionally using the Live Evaluation API in #1969 Signed-off-by: Michael Ehab Mikhail <[email protected]>
1 parent dcb0511 commit 3f17fca

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

vulnerabilities/importers/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
from vulnerabilities.pipelines.v2_importers import nvd_importer as nvd_importer_v2
5656
from vulnerabilities.pipelines.v2_importers import oss_fuzz as oss_fuzz_v2
5757
from vulnerabilities.pipelines.v2_importers import postgresql_importer as postgresql_importer_v2
58+
from vulnerabilities.pipelines.v2_importers import (
59+
postgresql_live_importer as postgresql_live_importer_v2,
60+
)
5861
from vulnerabilities.pipelines.v2_importers import pypa_importer as pypa_importer_v2
5962
from vulnerabilities.pipelines.v2_importers import pysec_importer as pysec_importer_v2
6063
from vulnerabilities.pipelines.v2_importers import redhat_importer as redhat_importer_v2
@@ -117,3 +120,9 @@
117120
oss_fuzz.OSSFuzzImporter,
118121
]
119122
)
123+
124+
LIVE_IMPORTERS_REGISTRY = create_registry(
125+
[
126+
postgresql_live_importer_v2.PostgreSQLLiveImporterPipeline,
127+
]
128+
)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
6+
#
7+
8+
import logging
9+
from typing import Iterable
10+
11+
from packageurl import PackageURL
12+
from univers.versions import GenericVersion
13+
from univers.versions import SemverVersion
14+
15+
from vulnerabilities.importer import AdvisoryData
16+
from vulnerabilities.pipelines.v2_importers.postgresql_importer import PostgreSQLImporterPipeline
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
class PostgreSQLLiveImporterPipeline(PostgreSQLImporterPipeline):
22+
"""
23+
Live importer for PostgreSQL that filters the batch output to a single PURL.
24+
"""
25+
26+
pipeline_id = "postgresql_live_importer_v2"
27+
supported_types = ["generic"]
28+
29+
@classmethod
30+
def steps(cls):
31+
return (
32+
cls.get_purl_inputs,
33+
cls.collect_and_store_advisories,
34+
)
35+
36+
def get_purl_inputs(self):
37+
purl = self.inputs.get("purl")
38+
if not purl:
39+
raise ValueError("PURL is required for PostgreSQLLiveImporterPipeline")
40+
41+
if isinstance(purl, str):
42+
purl = PackageURL.from_string(purl)
43+
44+
if not isinstance(purl, PackageURL):
45+
raise ValueError(f"Object of type {type(purl)} {purl!r} is not a PackageURL instance")
46+
47+
if purl.type not in self.supported_types:
48+
raise ValueError(
49+
f"PURL: {purl!s} is not among the supported package types {self.supported_types!r}"
50+
)
51+
52+
if purl.name != "postgresql":
53+
raise ValueError(f"PURL: {purl!s} is expected to be for 'postgresql'")
54+
55+
if not purl.version:
56+
raise ValueError(f"PURL: {purl!s} is expected to have a version")
57+
58+
self.purl = purl
59+
60+
def collect_advisories(self) -> Iterable[AdvisoryData]:
61+
for advisory in super().collect_advisories():
62+
if self._advisory_affects_purl(advisory):
63+
yield advisory
64+
65+
def _advisory_affects_purl(self, advisory: AdvisoryData) -> bool:
66+
if not advisory.affected_packages:
67+
return False
68+
69+
try:
70+
package_semver_version = SemverVersion(self.purl.version)
71+
package_generic_version = GenericVersion(self.purl.version)
72+
except Exception as e:
73+
logger.debug(f"Invalid PURL version {self.purl.version!r}: {e}")
74+
return False
75+
76+
for ap in advisory.affected_packages:
77+
if ap.package.type != "generic" or ap.package.name != "postgresql":
78+
continue
79+
80+
purl_q = self.purl.qualifiers or None
81+
ap_q = ap.package.qualifiers or None
82+
83+
if purl_q is None and ap_q is None:
84+
qualifiers_match = True
85+
else:
86+
qualifiers_match = all(ap_q.get(k) == v for k, v in purl_q.items())
87+
88+
if not qualifiers_match:
89+
continue
90+
91+
try:
92+
if getattr(ap, "affected_version_range", None):
93+
if package_semver_version in ap.affected_version_range:
94+
return True
95+
elif getattr(ap, "fixed_version", None):
96+
if package_generic_version < ap.fixed_version:
97+
return True
98+
except Exception as e:
99+
logger.debug(f"Version comparison failed for {package_semver_version}: {e}")
100+
continue
101+
102+
return False
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
import pytest
8+
import requests
9+
from packageurl import PackageURL
10+
11+
from vulnerabilities.importer import AdvisoryData
12+
from vulnerabilities.pipelines.v2_importers.postgresql_live_importer import (
13+
PostgreSQLLiveImporterPipeline,
14+
)
15+
16+
HTML_BASE = """
17+
<html>
18+
<body>
19+
<table>
20+
<tbody>
21+
<tr>
22+
<td>
23+
<span class="nobr"><a href="/support/security/CVE-2022-1234/">CVE-2022-1234</a></span><br>
24+
<a href="/about/news/postgresql-175-169-1513-1418-and-1321-released-3072/">Announcement</a><br>
25+
</td>
26+
<td>{affected}</td>
27+
<td>{fixed}</td>
28+
<td><a href="/vector?vector=CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H">9.8</a></td>
29+
<td>{summary}</td>
30+
</tr>
31+
</tbody>
32+
</table>
33+
</body>
34+
</html>
35+
"""
36+
37+
38+
class DummyResponse:
39+
def __init__(self, content):
40+
self.content = content.encode("utf-8")
41+
42+
43+
def test_affected_version(monkeypatch):
44+
html = HTML_BASE.format(affected="10.0, 10.1", fixed="10.2", summary="Issue affects all")
45+
monkeypatch.setattr(requests, "get", lambda url: DummyResponse(html))
46+
47+
purl = PackageURL(type="generic", name="postgresql", version="10.1")
48+
pipeline = PostgreSQLLiveImporterPipeline(purl=purl)
49+
pipeline.get_purl_inputs()
50+
advisories = list(pipeline.collect_advisories())
51+
52+
assert len(advisories) == 1
53+
adv = advisories[0]
54+
assert isinstance(adv, AdvisoryData)
55+
assert adv.advisory_id == "CVE-2022-1234"
56+
57+
58+
def test_unaffected_version(monkeypatch):
59+
html = HTML_BASE.format(affected="10.0, 10.1", fixed="10.2", summary="Issue affects all")
60+
monkeypatch.setattr(requests, "get", lambda url: DummyResponse(html))
61+
62+
purl = PackageURL(type="generic", name="postgresql", version="10.2")
63+
pipeline = PostgreSQLLiveImporterPipeline(purl=purl)
64+
pipeline.get_purl_inputs()
65+
advisories = list(pipeline.collect_advisories())
66+
67+
assert len(advisories) == 0
68+
69+
70+
def test_qualifier_filtering(monkeypatch):
71+
html = HTML_BASE.format(affected="12.0, 12.1", fixed="12.2", summary="Windows-specific issue")
72+
monkeypatch.setattr(requests, "get", lambda url: DummyResponse(html))
73+
74+
purl = PackageURL(
75+
type="generic", name="postgresql", version="12.1", qualifiers={"os": "windows"}
76+
)
77+
pipeline = PostgreSQLLiveImporterPipeline(purl=purl)
78+
pipeline.get_purl_inputs()
79+
advisories = list(pipeline.collect_advisories())
80+
assert len(advisories) == 1
81+
82+
purl = PackageURL(type="generic", name="postgresql", version="12.1", qualifiers={"os": "linux"})
83+
pipeline = PostgreSQLLiveImporterPipeline(purl=purl)
84+
pipeline.get_purl_inputs()
85+
advisories = list(pipeline.collect_advisories())
86+
assert len(advisories) == 0
87+
88+
89+
def test_invalid_purl():
90+
pipeline = PostgreSQLLiveImporterPipeline()
91+
92+
pipeline.inputs = {"purl": "pkg:pypi/[email protected]"}
93+
with pytest.raises(ValueError):
94+
pipeline.get_purl_inputs()
95+
96+
pipeline.inputs = {"purl": "pkg:generic/[email protected]"}
97+
with pytest.raises(ValueError):
98+
pipeline.get_purl_inputs()
99+
100+
pipeline.inputs = {"purl": "pkg:generic/postgresql"}
101+
with pytest.raises(ValueError):
102+
pipeline.get_purl_inputs()

0 commit comments

Comments
 (0)