From cd3d2c2de8d25abfcd1a388f0b236286639f3d29 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Wed, 4 Jan 2023 01:59:24 +0530 Subject: [PATCH 1/2] Support incomplete versions for a valid purl in search Signed-off-by: Tushar Goel --- CHANGELOG.rst | 3 ++ vulnerabilities/models.py | 9 +++-- vulnerabilities/tests/test_view.py | 60 ++++++++++++++++++++++++++++++ vulnerabilities/utils.py | 15 ++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 19707af2c..a5ac3e847 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,9 @@ Version v31.1.1 --------------- - We re-enabled support for the Apache HTTPD security advisories importer. +- We now support incomplete versions for a valid purl in search. For example, + you can now search for ``pkg:nginx/nginx@1`` and get all versions of nginx + starting with ``1``. Version v31.1.0 diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 8310aece0..d74093e0e 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -36,6 +36,7 @@ from vulnerabilities.improver import MAX_CONFIDENCE from vulnerabilities.severity_systems import SCORING_SYSTEMS from vulnerabilities.utils import build_vcid +from vulnerabilities.utils import remove_qualifiers_and_subpath logger = logging.getLogger(__name__) @@ -417,7 +418,8 @@ def search(self, query=None): try: # if it's a valid purl, use it as is purl = PackageURL.from_string(query) - return self.for_purl(purl, with_qualifiers_and_subpath=False) + purl = str(remove_qualifiers_and_subpath(purl)) + return qs.filter(package_url__istartswith=purl) except ValueError: return qs.filter(package_url__icontains=query) @@ -427,10 +429,9 @@ def for_purl(self, purl, with_qualifiers_and_subpath=True): """ if not isinstance(purl, PackageURL): purl = PackageURL.from_string(purl) - purl = purl_to_dict(purl) if not with_qualifiers_and_subpath: - del purl["qualifiers"] - del purl["subpath"] + remove_qualifiers_and_subpath(purl) + purl = purl_to_dict(purl) return self.filter(**purl) def with_cpes(self): diff --git a/vulnerabilities/tests/test_view.py b/vulnerabilities/tests/test_view.py index 44030408c..c3f15cdf8 100644 --- a/vulnerabilities/tests/test_view.py +++ b/vulnerabilities/tests/test_view.py @@ -34,6 +34,7 @@ def setUp(self): "pkg:nginx/nginx@1.14.1", "pkg:nginx/nginx@1.0.7", "pkg:nginx/nginx@1.0.15", + "pkg:pypi/foo@1", ] self.packages = packages for package in packages: @@ -63,6 +64,65 @@ def test_package_view_with_purl_fragment(self): self.assertEqual(len(pkgs), 1) self.assertEqual(pkgs[0].purl, "pkg:nginx/nginx@1.0.15") + def test_package_view_with_purl_fragment(self): + qs = PackageSearch().get_queryset(query="nginx/nginx") + pkgs = list(qs) + pkgs = [p.purl for p in pkgs] + assert pkgs == [ + "pkg:nginx/nginx@0.6.18", + "pkg:nginx/nginx@1.20.0", + "pkg:nginx/nginx@1.21.0", + "pkg:nginx/nginx@1.20.1", + "pkg:nginx/nginx@1.9.5", + "pkg:nginx/nginx@1.17.2", + "pkg:nginx/nginx@1.17.3", + "pkg:nginx/nginx@1.16.1", + "pkg:nginx/nginx@1.15.5", + "pkg:nginx/nginx@1.15.6", + "pkg:nginx/nginx@1.14.1", + "pkg:nginx/nginx@1.0.7", + "pkg:nginx/nginx@1.0.15", + ] + + def test_package_view_with_valid_purl_without_version(self): + qs = PackageSearch().get_queryset(query="pkg:nginx/nginx") + pkgs = list(qs) + pkgs = [p.purl for p in pkgs] + assert pkgs == [ + "pkg:nginx/nginx@0.6.18", + "pkg:nginx/nginx@1.20.0", + "pkg:nginx/nginx@1.21.0", + "pkg:nginx/nginx@1.20.1", + "pkg:nginx/nginx@1.9.5", + "pkg:nginx/nginx@1.17.2", + "pkg:nginx/nginx@1.17.3", + "pkg:nginx/nginx@1.16.1", + "pkg:nginx/nginx@1.15.5", + "pkg:nginx/nginx@1.15.6", + "pkg:nginx/nginx@1.14.1", + "pkg:nginx/nginx@1.0.7", + "pkg:nginx/nginx@1.0.15", + ] + + def test_package_view_with_valid_purl_and_incomplete_version(self): + qs = PackageSearch().get_queryset(query="pkg:nginx/nginx@1") + pkgs = list(qs) + pkgs = [p.purl for p in pkgs] + assert pkgs == [ + "pkg:nginx/nginx@1.20.0", + "pkg:nginx/nginx@1.21.0", + "pkg:nginx/nginx@1.20.1", + "pkg:nginx/nginx@1.9.5", + "pkg:nginx/nginx@1.17.2", + "pkg:nginx/nginx@1.17.3", + "pkg:nginx/nginx@1.16.1", + "pkg:nginx/nginx@1.15.5", + "pkg:nginx/nginx@1.15.6", + "pkg:nginx/nginx@1.14.1", + "pkg:nginx/nginx@1.0.7", + "pkg:nginx/nginx@1.0.15", + ] + class VulnerabilitySearchTestCase(TestCase): def setUp(self): diff --git a/vulnerabilities/utils.py b/vulnerabilities/utils.py index 485a8366b..64c22e1de 100644 --- a/vulnerabilities/utils.py +++ b/vulnerabilities/utils.py @@ -420,3 +420,18 @@ def fetch_response(url): if response.status_code == 200: return response raise Exception(f"Failed to fetch data from {url!r} with status code: {response.status_code!r}") + + +# This should be a method on PackageURL +def remove_qualifiers_and_subpath(purl): + """ + Return a package URL without qualifiers and subpath + """ + if not isinstance(purl, PackageURL): + purl = PackageURL.from_string(purl) + return PackageURL( + type=purl.type, + namespace=purl.namespace, + name=purl.name, + version=purl.version, + ) From 386b587a4016607c219ab8a11892dab47bfcbe8f Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Wed, 4 Jan 2023 20:02:39 +0530 Subject: [PATCH 2/2] Add tests for searching purl type Signed-off-by: Tushar Goel --- vulnerabilities/tests/test_view.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vulnerabilities/tests/test_view.py b/vulnerabilities/tests/test_view.py index c3f15cdf8..db511dd6b 100644 --- a/vulnerabilities/tests/test_view.py +++ b/vulnerabilities/tests/test_view.py @@ -123,6 +123,18 @@ def test_package_view_with_valid_purl_and_incomplete_version(self): "pkg:nginx/nginx@1.0.15", ] + def test_package_view_with_purl_type(self): + qs = PackageSearch().get_queryset(query="pkg:pypi") + pkgs = list(qs) + pkgs = [p.purl for p in pkgs] + assert pkgs == ["pkg:pypi/foo@1"] + + def test_package_view_with_type_as_input(self): + qs = PackageSearch().get_queryset(query="pypi") + pkgs = list(qs) + pkgs = [p.purl for p in pkgs] + assert pkgs == ["pkg:pypi/foo@1"] + class VulnerabilitySearchTestCase(TestCase): def setUp(self):