2222from typing import Set
2323from typing import Tuple
2424
25+ import pytz
26+ from dateutil import parser as dateparser
2527from fetchcode .vcs import fetch_via_vcs
2628from license_expression import Licensing
2729from packageurl import PackageURL
30+ from univers .version_range import RANGE_CLASS_BY_SCHEMES
2831from univers .version_range import VersionRange
2932from univers .versions import Version
3033
34+ from vulnerabilities import severity_systems
3135from vulnerabilities .oval_parser import OvalParser
3236from vulnerabilities .severity_systems import SCORING_SYSTEMS
3337from vulnerabilities .severity_systems import ScoringSystem
@@ -350,13 +354,13 @@ class OvalImporter(Importer):
350354 """
351355
352356 @staticmethod
353- def create_purl (pkg_name : str , pkg_version : str , pkg_data : Mapping ) -> PackageURL :
357+ def create_purl (pkg_name : str , pkg_data : Mapping ) -> PackageURL :
354358 """
355359 Helper method for creating different purls for subclasses without them reimplementing
356360 get_data_from_xml_doc method
357361 Note: pkg_data must include 'type' of package
358362 """
359- return PackageURL (name = pkg_name , version = pkg_version , ** pkg_data )
363+ return PackageURL (name = pkg_name , ** pkg_data )
360364
361365 @staticmethod
362366 def _collect_pkgs (parsed_oval_data : Mapping ) -> Set :
@@ -390,28 +394,17 @@ def advisory_data(self) -> List[AdvisoryData]:
390394 for metadata , oval_file in self ._fetch ():
391395 try :
392396 oval_data = self .get_data_from_xml_doc (oval_file , metadata )
393- yield oval_data
397+ yield from oval_data
394398 except Exception :
395399 logger .error (
396400 f"Failed to get updated_advisories: { oval_file !r} "
397401 f"with { metadata !r} :\n " + traceback .format_exc ()
398402 )
399403 continue
400404
401- def set_api (self , all_pkgs : Iterable [str ]):
402- """
403- This method loads the self.pkg_manager_api with the specified packages.
404- It fetches and caches all the versions of these packages and exposes
405- them through self.pkg_manager_api.get(<package_name>). Example
406-
407- >> self.set_api(['electron'])
408- Assume 'electron' has only versions 1.0.0 and 1.2.0
409- >> assert self.pkg_manager_api.get('electron') == {'1.0.0','1.2.0'}
410-
411- """
412- raise NotImplementedError
413-
414- def get_data_from_xml_doc (self , xml_doc : ET .ElementTree , pkg_metadata = {}) -> List [AdvisoryData ]:
405+ def get_data_from_xml_doc (
406+ self , xml_doc : ET .ElementTree , pkg_metadata = {}
407+ ) -> Iterable [AdvisoryData ]:
415408 """
416409 The orchestration method of the OvalDataSource. This method breaks an
417410 OVAL xml ElementTree into a list of `Advisory`.
@@ -422,66 +415,58 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
422415 Example value of pkg_metadata:
423416 {"type":"deb","qualifiers":{"distro":"buster"} }
424417 """
425-
426- all_adv = []
427- oval_doc = OvalParser (self .translations , xml_doc )
428- raw_data = oval_doc .get_data ()
429- all_pkgs = self ._collect_pkgs (raw_data )
430- self .set_api (all_pkgs )
418+ oval_parsed_data = OvalParser (self .translations , xml_doc )
419+ raw_data = oval_parsed_data .get_data ()
420+ oval_doc = oval_parsed_data .oval_document
421+ timestamp = oval_doc .getGenerator ().getTimestamp ()
431422
432423 # convert definition_data to Advisory objects
433424 for definition_data in raw_data :
434425 # These fields are definition level, i.e common for all elements
435426 # connected/linked to an OvalDefinition
436427 vuln_id = definition_data ["vuln_id" ]
437428 description = definition_data ["description" ]
438- references = [Reference (url = url ) for url in definition_data ["reference_urls" ]]
429+ severities = (
430+ [
431+ VulnerabilitySeverity (
432+ system = severity_systems .GENERIC , value = definition_data .get ("severity" )
433+ )
434+ ]
435+ if definition_data .get ("severity" )
436+ else []
437+ )
438+ references = [
439+ Reference (url = url , severities = severities )
440+ for url in definition_data ["reference_urls" ]
441+ ]
439442 affected_packages = []
440443 for test_data in definition_data ["test_data" ]:
441444 for package_name in test_data ["package_list" ]:
442- if package_name and len (package_name ) >= 50 :
443- continue
444-
445- affected_version_range = test_data ["version_ranges" ] or set ()
446- version_class = version_class_by_package_type [pkg_metadata ["type" ]]
447- version_scheme = version_class .scheme
448-
449- affected_version_range = VersionRange .from_scheme_version_spec_string (
450- version_scheme , affected_version_range
451- )
452- all_versions = self .pkg_manager_api .get (package_name ).valid_versions
453-
454- # FIXME: what is this 50 DB limit? that's too small for versions
455- # FIXME: we should not drop data this way
456- # This filter is for filtering out long versions.
457- # 50 is limit because that's what db permits atm.
458- all_versions = [version for version in all_versions if len (version ) < 50 ]
459- if not all_versions :
460- continue
461-
462- affected_purls = []
463- safe_purls = []
464- for version in all_versions :
465- purl = self .create_purl (
466- pkg_name = package_name ,
467- pkg_version = version ,
468- pkg_data = pkg_metadata ,
445+ affected_version_range = test_data ["version_ranges" ]
446+ vrc = RANGE_CLASS_BY_SCHEMES [pkg_metadata ["type" ]]
447+ if affected_version_range :
448+ try :
449+ affected_version_range = vrc .from_native (affected_version_range )
450+ except Exception as e :
451+ logger .error (
452+ f"Failed to parse version range { affected_version_range !r} "
453+ f"for package { package_name !r} :\n { e } "
454+ )
455+ continue
456+ if package_name :
457+ affected_packages .append (
458+ AffectedPackage (
459+ package = self .create_purl (package_name , pkg_metadata ),
460+ affected_version_range = affected_version_range ,
461+ )
469462 )
470- if version_class (version ) in affected_version_range :
471- affected_purls .append (purl )
472- else :
473- safe_purls .append (purl )
474-
475- affected_packages .extend (
476- nearest_patched_package (affected_purls , safe_purls ),
477- )
478-
479- all_adv .append (
480- AdvisoryData (
481- summary = description ,
482- affected_packages = affected_packages ,
483- vulnerability_id = vuln_id ,
484- references = references ,
485- )
463+ date_published = dateparser .parse (timestamp )
464+ if not date_published .tzinfo :
465+ date_published = date_published .replace (tzinfo = pytz .UTC )
466+ yield AdvisoryData (
467+ aliases = [vuln_id ],
468+ summary = description ,
469+ affected_packages = affected_packages ,
470+ references = sorted (references ),
471+ date_published = date_published ,
486472 )
487- return all_adv
0 commit comments