@@ -253,8 +253,8 @@ def from_dict(cls, data: dict):
253253@dataclasses .dataclass (eq = True )
254254@functools .total_ordering
255255class PatchData :
256- patch_url : Optional [str ] = None
257- patch_text : Optional [str ] = None
256+ patch_url : Optional [str ] = ""
257+ patch_text : Optional [str ] = ""
258258 patch_checksum : Optional [str ] = dataclasses .field (init = False , default = None )
259259
260260 def __post_init__ (self ):
@@ -271,9 +271,9 @@ def __lt__(self, other):
271271
272272 def _cmp_key (self ):
273273 return (
274- self .patch_url ,
275- self .patch_text ,
276- self .patch_checksum ,
274+ self .patch_url or "" ,
275+ self .patch_text or "" ,
276+ self .patch_checksum or "" ,
277277 )
278278
279279 def to_dict (self ) -> dict :
@@ -556,23 +556,63 @@ def from_dict(cls, affected_pkg: dict):
556556class AdvisoryData :
557557 """
558558 This data class expresses the contract between data sources and the import runner.
559+ """
560+
561+ aliases : List [str ] = dataclasses .field (default_factory = list )
562+ summary : Optional [str ] = ""
563+ affected_packages : List [AffectedPackage ] = dataclasses .field (default_factory = list )
564+ references : List [Reference ] = dataclasses .field (default_factory = list )
565+ date_published : Optional [datetime .datetime ] = None
566+ weaknesses : List [int ] = dataclasses .field (default_factory = list )
567+ url : Optional [str ] = None
568+
569+ def __post_init__ (self ):
570+ if self .summary :
571+ self .summary = clean_summary (self .summary )
559572
560- If a vulnerability_id is present then:
561- summary or affected_packages or references must be present
562- otherwise
563- either affected_package or references should be present
573+ def to_dict (self ):
574+ return {
575+ "aliases" : self .aliases ,
576+ "summary" : self .summary ,
577+ "affected_packages" : [pkg .to_dict () for pkg in self .affected_packages ],
578+ "references" : [ref .to_dict () for ref in self .references ],
579+ "date_published" : self .date_published .isoformat () if self .date_published else None ,
580+ "weaknesses" : self .weaknesses ,
581+ "url" : self .url if self .url else "" ,
582+ }
564583
565- date_published must be aware datetime
584+ @classmethod
585+ def from_dict (cls , advisory_data ):
586+ date_published = advisory_data ["date_published" ]
587+ transformed = {
588+ "aliases" : advisory_data ["aliases" ],
589+ "summary" : advisory_data ["summary" ],
590+ "affected_packages" : [
591+ AffectedPackage .from_dict (pkg )
592+ for pkg in advisory_data ["affected_packages" ]
593+ if pkg is not None
594+ ],
595+ "references" : [Reference .from_dict (ref ) for ref in advisory_data ["references" ]],
596+ "date_published" : datetime .datetime .fromisoformat (date_published )
597+ if date_published
598+ else None ,
599+ "weaknesses" : advisory_data ["weaknesses" ],
600+ "url" : advisory_data .get ("url" ) or None ,
601+ }
602+ return cls (** transformed )
603+
604+
605+ @dataclasses .dataclass (order = True )
606+ class AdvisoryDataV2 :
607+ """
608+ This data class expresses the contract between data sources and the import runner.
566609 """
567610
568611 advisory_id : str = ""
569612 aliases : List [str ] = dataclasses .field (default_factory = list )
570613 summary : Optional [str ] = ""
571- affected_packages : Union [List [AffectedPackage ], List [AffectedPackageV2 ]] = dataclasses .field (
572- default_factory = list
573- )
574- references : List [Reference ] = dataclasses .field (default_factory = list )
575- references_v2 : List [ReferenceV2 ] = dataclasses .field (default_factory = list )
614+ affected_packages : List [AffectedPackageV2 ] = dataclasses .field (default_factory = list )
615+ references : List [ReferenceV2 ] = dataclasses .field (default_factory = list )
576616 patches : List [PatchData ] = dataclasses .field (default_factory = list )
577617 date_published : Optional [datetime .datetime ] = None
578618 weaknesses : List [int ] = dataclasses .field (default_factory = list )
@@ -581,46 +621,24 @@ class AdvisoryData:
581621 original_advisory_text : Optional [str ] = None
582622
583623 def __post_init__ (self ):
624+ if not self .advisory_id :
625+ raise ValueError ("advisory_id is required for AdvisoryDataV2" )
584626 if self .advisory_id and self .advisory_id in self .aliases :
585627 raise ValueError (
586628 f"advisory_id { self .advisory_id } should not be present in aliases { self .aliases } "
587629 )
588630 if self .summary :
589- self .summary = self .clean_summary (self .summary )
590-
591- def clean_summary (self , summary ):
592- # https://nvd.nist.gov/vuln/detail/CVE-2013-4314
593- # https://github.com/cms-dev/cms/issues/888#issuecomment-516977572
594- summary = summary .strip ()
595- if summary :
596- summary = summary .replace ("\x00 " , "\uFFFD " )
597- return summary
631+ self .summary = clean_summary (self .summary )
598632
599633 def to_dict (self ):
600- is_adv_v2 = (
601- self .advisory_id
602- or self .severities
603- or self .references_v2
604- or (self .affected_packages and isinstance (self .affected_packages [0 ], AffectedPackageV2 ))
605- )
606- if is_adv_v2 :
607- return {
608- "advisory_id" : self .advisory_id ,
609- "aliases" : self .aliases ,
610- "summary" : self .summary ,
611- "affected_packages" : [pkg .to_dict () for pkg in self .affected_packages ],
612- "references_v2" : [ref .to_dict () for ref in self .references_v2 ],
613- "patches" : [patch .to_dict () for patch in self .patches ],
614- "severities" : [sev .to_dict () for sev in self .severities ],
615- "date_published" : self .date_published .isoformat () if self .date_published else None ,
616- "weaknesses" : self .weaknesses ,
617- "url" : self .url if self .url else "" ,
618- }
619634 return {
635+ "advisory_id" : self .advisory_id ,
620636 "aliases" : self .aliases ,
621637 "summary" : self .summary ,
622638 "affected_packages" : [pkg .to_dict () for pkg in self .affected_packages ],
623639 "references" : [ref .to_dict () for ref in self .references ],
640+ "patches" : [patch .to_dict () for patch in self .patches ],
641+ "severities" : [sev .to_dict () for sev in self .severities ],
624642 "date_published" : self .date_published .isoformat () if self .date_published else None ,
625643 "weaknesses" : self .weaknesses ,
626644 "url" : self .url if self .url else "" ,
@@ -629,31 +647,37 @@ def to_dict(self):
629647 @classmethod
630648 def from_dict (cls , advisory_data ):
631649 date_published = advisory_data ["date_published" ]
632- affected_packages = advisory_data ["affected_packages" ]
633- affected_package_cls = AffectedPackage
634- if affected_packages :
635- affected_package_cls = (
636- AffectedPackageV2
637- if "fixed_version_range" in affected_packages [0 ]
638- else AffectedPackage
639- )
640650 transformed = {
641651 "aliases" : advisory_data ["aliases" ],
642652 "summary" : advisory_data ["summary" ],
643653 "affected_packages" : [
644- affected_package_cls .from_dict (pkg ) for pkg in affected_packages if pkg is not None
654+ AffectedPackageV2 .from_dict (pkg )
655+ for pkg in advisory_data ["affected_packages" ]
656+ if pkg is not None
645657 ],
646658 "patches" : [PatchData .from_dict (patch ) for patch in advisory_data .get ("patches" , [])],
647- "references" : [Reference .from_dict (ref ) for ref in advisory_data ["references" ]],
659+ "references" : [ReferenceV2 .from_dict (ref ) for ref in advisory_data ["references" ]],
648660 "date_published" : datetime .datetime .fromisoformat (date_published )
649661 if date_published
650662 else None ,
651663 "weaknesses" : advisory_data ["weaknesses" ],
664+ "severities" : [
665+ VulnerabilitySeverity .from_dict (sev ) for sev in advisory_data .get ("severities" , [])
666+ ],
652667 "url" : advisory_data .get ("url" ) or None ,
653668 }
654669 return cls (** transformed )
655670
656671
672+ def clean_summary (summary ):
673+ # https://nvd.nist.gov/vuln/detail/CVE-2013-4314
674+ # https://github.com/cms-dev/cms/issues/888#issuecomment-516977572
675+ summary = summary .strip ()
676+ if summary :
677+ summary = summary .replace ("\x00 " , "\uFFFD " )
678+ return summary
679+
680+
657681class NoLicenseError (Exception ):
658682 pass
659683
0 commit comments