|
7 | 7 | # See https://aboutcode.org for more information about nexB OSS projects. |
8 | 8 | # |
9 | 9 |
|
10 | | -import asyncio |
| 10 | +import logging |
11 | 11 | import urllib |
| 12 | +from datetime import datetime |
| 13 | +from typing import Iterable |
| 14 | +from typing import List |
| 15 | +from typing import Mapping |
| 16 | +from typing import Optional |
12 | 17 |
|
13 | 18 | import requests |
14 | 19 | from bs4 import BeautifulSoup |
| 20 | +from django.db.models.query import QuerySet |
15 | 21 | from packageurl import PackageURL |
16 | 22 | from univers.version_constraint import VersionConstraint |
17 | 23 | from univers.version_range import ApacheVersionRange |
|
21 | 27 | from vulnerabilities.importer import AffectedPackage |
22 | 28 | from vulnerabilities.importer import Importer |
23 | 29 | from vulnerabilities.importer import Reference |
| 30 | +from vulnerabilities.importer import UnMergeablePackageError |
24 | 31 | from vulnerabilities.importer import VulnerabilitySeverity |
| 32 | +from vulnerabilities.improver import Improver |
| 33 | +from vulnerabilities.improver import Inference |
| 34 | +from vulnerabilities.models import Advisory |
| 35 | +from vulnerabilities.package_managers import GitHubTagsAPI |
| 36 | +from vulnerabilities.package_managers import VersionAPI |
25 | 37 | from vulnerabilities.severity_systems import APACHE_HTTPD |
| 38 | +from vulnerabilities.utils import AffectedPackage as LegacyAffectedPackage |
| 39 | +from vulnerabilities.utils import get_affected_packages_by_patched_package |
| 40 | +from vulnerabilities.utils import nearest_patched_package |
| 41 | +from vulnerabilities.utils import resolve_version_range |
| 42 | + |
| 43 | +logger = logging.getLogger(__name__) |
26 | 44 |
|
27 | 45 |
|
28 | 46 | class ApacheHTTPDImporter(Importer): |
@@ -147,7 +165,7 @@ def fetch_links(url): |
147 | 165 | return links |
148 | 166 |
|
149 | 167 |
|
150 | | -ignore_tags = { |
| 168 | +IGNORE_TAGS = { |
151 | 169 | "AGB_BEFORE_AAA_CHANGES", |
152 | 170 | "APACHE_1_2b1", |
153 | 171 | "APACHE_1_2b10", |
@@ -209,3 +227,80 @@ def fetch_links(url): |
209 | 227 | "post_ajp_proxy", |
210 | 228 | "pre_ajp_proxy", |
211 | 229 | } |
| 230 | + |
| 231 | + |
| 232 | +class ApacheHTTPDImprover(Improver): |
| 233 | + def __init__(self) -> None: |
| 234 | + self.versions_fetcher_by_purl: Mapping[str, VersionAPI] = {} |
| 235 | + self.vesions_by_purl = {} |
| 236 | + |
| 237 | + @property |
| 238 | + def interesting_advisories(self) -> QuerySet: |
| 239 | + return Advisory.objects.filter(created_by=ApacheHTTPDImporter.qualified_name) |
| 240 | + |
| 241 | + def get_package_versions( |
| 242 | + self, package_url: PackageURL, until: Optional[datetime] = None |
| 243 | + ) -> List[str]: |
| 244 | + """ |
| 245 | + Return a list of `valid_versions` for the `package_url` |
| 246 | + """ |
| 247 | + api_name = "apache/httpd" |
| 248 | + versions_fetcher = GitHubTagsAPI() |
| 249 | + return versions_fetcher.get_until(package_name=api_name, until=until).valid_versions |
| 250 | + |
| 251 | + def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]: |
| 252 | + """ |
| 253 | + Yield Inferences for the given advisory data |
| 254 | + """ |
| 255 | + if not advisory_data.affected_packages: |
| 256 | + return |
| 257 | + try: |
| 258 | + purl, affected_version_ranges, _ = AffectedPackage.merge( |
| 259 | + advisory_data.affected_packages |
| 260 | + ) |
| 261 | + except UnMergeablePackageError: |
| 262 | + logger.error(f"Cannot merge with different purls {advisory_data.affected_packages!r}") |
| 263 | + return iter([]) |
| 264 | + |
| 265 | + pkg_type = purl.type |
| 266 | + pkg_namespace = purl.namespace |
| 267 | + pkg_name = purl.name |
| 268 | + |
| 269 | + if not self.vesions_by_purl.get(str(purl)): |
| 270 | + valid_versions = self.get_package_versions( |
| 271 | + package_url=purl, until=advisory_data.date_published |
| 272 | + ) |
| 273 | + self.vesions_by_purl[str(purl)] = valid_versions |
| 274 | + |
| 275 | + valid_versions = self.vesions_by_purl[str(purl)] |
| 276 | + |
| 277 | + for affected_version_range in affected_version_ranges: |
| 278 | + aff_vers, unaff_vers = resolve_version_range( |
| 279 | + affected_version_range=affected_version_range, |
| 280 | + package_versions=valid_versions, |
| 281 | + ignorable_versions=IGNORE_TAGS, |
| 282 | + ) |
| 283 | + affected_purls = [ |
| 284 | + PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version) |
| 285 | + for version in aff_vers |
| 286 | + ] |
| 287 | + |
| 288 | + unaffected_purls = [ |
| 289 | + PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version) |
| 290 | + for version in unaff_vers |
| 291 | + ] |
| 292 | + |
| 293 | + affected_packages: List[LegacyAffectedPackage] = nearest_patched_package( |
| 294 | + vulnerable_packages=affected_purls, resolved_packages=unaffected_purls |
| 295 | + ) |
| 296 | + |
| 297 | + for ( |
| 298 | + fixed_package, |
| 299 | + affected_packages, |
| 300 | + ) in get_affected_packages_by_patched_package(affected_packages).items(): |
| 301 | + yield Inference.from_advisory_data( |
| 302 | + advisory_data, |
| 303 | + confidence=100, # We are getting all valid versions to get this inference |
| 304 | + affected_purls=affected_packages, |
| 305 | + fixed_purl=fixed_package, |
| 306 | + ) |
0 commit comments