Skip to content

Commit 33e083c

Browse files
authored
Merge pull request #642 from TG1999/migration/github
Reference: #604 Migrate github importer to importer-improver model
2 parents 8c69661 + b6aad46 commit 33e083c

31 files changed

+5141
-655
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ django-widget-tweaks>=1.4.8
88
packageurl-python>=0.9.4
99
binaryornot>=0.4.4
1010
GitPython>=3.1.17
11-
univers>=30.1.0
11+
univers>=30.3.1
1212
saneyaml>=0.5.2
1313
beautifulsoup4>=4.9.3
1414
python-dateutil>=2.8.1

vulnerabilities/helpers.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import bisect
2424
import dataclasses
2525
import json
26+
import logging
2627
import re
28+
from functools import total_ordering
2729
from typing import List
2830
from typing import Optional
2931
from typing import Tuple
@@ -34,8 +36,9 @@
3436
import toml
3537
import urllib3
3638
from packageurl import PackageURL
39+
from univers.version_range import RANGE_CLASS_BY_SCHEMES
3740

38-
# TODO add logging here
41+
LOGGER = logging.getLogger(__name__)
3942

4043
cve_regex = re.compile(r"CVE-\d{4}-\d{4,7}", re.IGNORECASE)
4144
is_cve = cve_regex.match
@@ -133,32 +136,36 @@ def requests_with_5xx_retry(max_retries=5, backoff_factor=0.5):
133136
return session
134137

135138

136-
def nearest_patched_package(
137-
vulnerable_packages: List[PackageURL], resolved_packages: List[PackageURL]
138-
) -> List[AffectedPackage]:
139-
class PackageURLWithVersionComparator:
140-
"""
141-
This class is used to get around bisect module's lack of supplying custom
142-
compartor. Get rid of this once we use python 3.10 which supports this.
143-
See https://github.com/python/cpython/pull/20556
144-
"""
139+
@total_ordering
140+
class VersionedPackage:
141+
"""
142+
A PackageURL with a Version class.
143+
This class is used to get around bisect module's lack of supplying custom
144+
comparator. Get rid of this once we use python 3.10 which supports this.
145+
See https://github.com/python/cpython/pull/20556
146+
"""
145147

146-
def __init__(self, package):
147-
self.package = package
148-
self.version_object = version_class_by_package_type[package.type](package.version)
148+
def __init__(self, purl: PackageURL):
149+
self.purl = purl
150+
vrc = RANGE_CLASS_BY_SCHEMES.get(purl.type)
151+
self.version = vrc.version_class(purl.version)
149152

150-
def __eq__(self, other):
151-
return self.version_object == other.version_object
153+
def __eq__(self, other):
154+
return self.version == other.version
152155

153-
def __lt__(self, other):
154-
return self.version_object < other.version_object
156+
def __lt__(self, other):
157+
return self.version < other.version
155158

156-
vulnerable_packages = sorted(
157-
[PackageURLWithVersionComparator(package) for package in vulnerable_packages]
158-
)
159-
resolved_packages = sorted(
160-
[PackageURLWithVersionComparator(package) for package in resolved_packages]
161-
)
159+
160+
def nearest_patched_package(
161+
vulnerable_packages: List[PackageURL], resolved_packages: List[PackageURL]
162+
) -> List[AffectedPackage]:
163+
"""
164+
Return a list of Affected Packages for each Patched package.
165+
"""
166+
167+
vulnerable_packages = sorted([VersionedPackage(package) for package in vulnerable_packages])
168+
resolved_packages = sorted([VersionedPackage(package) for package in resolved_packages])
162169

163170
resolved_package_count = len(resolved_packages)
164171
affected_package_with_patched_package_objects = []
@@ -167,11 +174,11 @@ def __lt__(self, other):
167174
patched_package_index = bisect.bisect_right(resolved_packages, vulnerable_package)
168175
patched_package = None
169176
if patched_package_index < resolved_package_count:
170-
patched_package = resolved_packages[patched_package_index].package
177+
patched_package = resolved_packages[patched_package_index]
171178

172179
affected_package_with_patched_package_objects.append(
173180
AffectedPackage(
174-
vulnerable_package=vulnerable_package.package, patched_package=patched_package
181+
vulnerable_package=vulnerable_package.purl, patched_package=patched_package.purl
175182
)
176183
)
177184

@@ -211,3 +218,26 @@ def __init__(self, fget):
211218

212219
def __get__(self, owner_self, owner_cls):
213220
return self.fget(owner_cls)
221+
222+
223+
def get_item(object: dict, *attributes):
224+
"""
225+
Return `item` by going through all the `attributes` present in the `json_object`
226+
227+
Do a DFS for the `item` in the `json_object` by traversing the `attributes`
228+
and return None if can not traverse through the `attributes`
229+
For example:
230+
>>> get_item({'a': {'b': {'c': 'd'}}}, 'a', 'b', 'c')
231+
'd'
232+
>>> assert(get_item({'a': {'b': {'c': 'd'}}}, 'a', 'b', 'e')) == None
233+
"""
234+
if not object:
235+
LOGGER.error(f"Object is empty: {object}")
236+
return
237+
item = object
238+
for attribute in attributes:
239+
if attribute not in item:
240+
LOGGER.error(f"Missing attribute {attribute} in {item}")
241+
return None
242+
item = item[attribute]
243+
return item

vulnerabilities/importer.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ def from_dict(cls, ref: dict):
113113
)
114114

115115

116+
class UnMergeablePackageError(Exception):
117+
"""
118+
Raised when a package cannot be merged with another one.
119+
"""
120+
121+
122+
class NoAffectedPackages(Exception):
123+
"""
124+
Raised when there were no affected packages found.
125+
"""
126+
127+
116128
@dataclasses.dataclass(order=True, frozen=True)
117129
class AffectedPackage:
118130
"""
@@ -155,18 +167,23 @@ def merge(cls, affected_packages: Iterable):
155167
affected_version_range: set(VersionRange)
156168
fixed_versions: set(Version)
157169
"""
158-
affected_version_ranges = set()
159-
fixed_versions = set()
170+
affected_packages = list(affected_packages)
171+
if not affected_packages:
172+
raise NoAffectedPackages("No affected packages found")
173+
affected_version_ranges = list()
174+
fixed_versions = list()
160175
purls = set()
161176
for pkg in affected_packages:
162177
if pkg.affected_version_range:
163-
affected_version_ranges.add(pkg.affected_version_range)
178+
if pkg.affected_version_range not in affected_version_ranges:
179+
affected_version_ranges.append(pkg.affected_version_range)
164180
if pkg.fixed_version:
165-
fixed_versions.add(pkg.fixed_version)
181+
if pkg.fixed_version not in fixed_versions:
182+
fixed_versions.append(pkg.fixed_version)
166183
purls.add(pkg.package)
167184
if len(purls) > 1:
168-
raise TypeError("Cannot merge with different purls", purls)
169-
return purls.pop(), affected_version_ranges, fixed_versions
185+
raise UnMergeablePackageError("Cannot merge with different purls", purls)
186+
return purls.pop(), sorted(affected_version_ranges), sorted(fixed_versions)
170187

171188
def to_dict(self):
172189
"""
@@ -230,6 +247,15 @@ def __post_init__(self):
230247
if self.date_published and not self.date_published.tzinfo:
231248
logger.warn(f"AdvisoryData with no tzinfo: {self!r}")
232249

250+
def to_dict(self):
251+
return {
252+
"aliases": self.aliases,
253+
"summary": self.summary,
254+
"affected_packages": [pkg.to_dict() for pkg in self.affected_packages],
255+
"references": [ref.to_dict() for ref in self.references],
256+
"date_published": self.date_published.isoformat() if self.date_published else None,
257+
}
258+
233259

234260
class NoLicenseError(Exception):
235261
pass

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222
from vulnerabilities.importers import alpine_linux
23+
from vulnerabilities.importers import github
2324
from vulnerabilities.importers import nginx
2425

25-
IMPORTERS_REGISTRY = [nginx.NginxImporter, alpine_linux.AlpineImporter]
26+
IMPORTERS_REGISTRY = [nginx.NginxImporter, alpine_linux.AlpineImporter, github.GitHubAPIImporter]
2627

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

0 commit comments

Comments
 (0)