Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support incomplete versions for a valid purl in search #1065

Merged
merged 2 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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)

Expand All @@ -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):
Expand Down
72 changes: 72 additions & 0 deletions vulnerabilities/tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def setUp(self):
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:pypi/foo@1",
]
self.packages = packages
for package in packages:
Expand Down Expand Up @@ -63,6 +64,77 @@ def test_package_view_with_purl_fragment(self):
self.assertEqual(len(pkgs), 1)
self.assertEqual(pkgs[0].purl, "pkg:nginx/[email protected]")

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/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
]

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/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
]

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/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
"pkg:nginx/[email protected]",
]

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):
Expand Down
15 changes: 15 additions & 0 deletions vulnerabilities/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)