@@ -565,14 +565,68 @@ class AdvisoryData:
565565 date_published must be aware datetime
566566 """
567567
568- advisory_id : str = ""
569568 aliases : List [str ] = dataclasses .field (default_factory = list )
570569 summary : Optional [str ] = ""
571- affected_packages : Union [List [AffectedPackage ], List [AffectedPackageV2 ]] = dataclasses .field (
572- default_factory = list
573- )
570+ affected_packages : List [AffectedPackage ] = dataclasses .field (default_factory = list )
574571 references : List [Reference ] = dataclasses .field (default_factory = list )
575- references_v2 : List [ReferenceV2 ] = dataclasses .field (default_factory = list )
572+ date_published : Optional [datetime .datetime ] = None
573+ weaknesses : List [int ] = dataclasses .field (default_factory = list )
574+ url : Optional [str ] = None
575+
576+ def __post_init__ (self ):
577+ if self .summary :
578+ self .summary = clean_summary (self .summary )
579+
580+ def to_dict (self ):
581+ return {
582+ "aliases" : self .aliases ,
583+ "summary" : self .summary ,
584+ "affected_packages" : [pkg .to_dict () for pkg in self .affected_packages ],
585+ "references" : [ref .to_dict () for ref in self .references ],
586+ "date_published" : self .date_published .isoformat () if self .date_published else None ,
587+ "weaknesses" : self .weaknesses ,
588+ "url" : self .url if self .url else "" ,
589+ }
590+
591+ @classmethod
592+ def from_dict (cls , advisory_data ):
593+ date_published = advisory_data ["date_published" ]
594+ transformed = {
595+ "aliases" : advisory_data ["aliases" ],
596+ "summary" : advisory_data ["summary" ],
597+ "affected_packages" : [
598+ AffectedPackage .from_dict (pkg )
599+ for pkg in advisory_data ["affected_packages" ]
600+ if pkg is not None
601+ ],
602+ "references" : [Reference .from_dict (ref ) for ref in advisory_data ["references" ]],
603+ "date_published" : datetime .datetime .fromisoformat (date_published )
604+ if date_published
605+ else None ,
606+ "weaknesses" : advisory_data ["weaknesses" ],
607+ "url" : advisory_data .get ("url" ) or None ,
608+ }
609+ return cls (** transformed )
610+
611+
612+ @dataclasses .dataclass (order = True )
613+ class AdvisoryDataV2 :
614+ """
615+ This data class expresses the contract between data sources and the import runner.
616+
617+ If a vulnerability_id is present then:
618+ summary or affected_packages or references must be present
619+ otherwise
620+ either affected_package or references should be present
621+
622+ date_published must be aware datetime
623+ """
624+
625+ advisory_id : str = ""
626+ aliases : List [str ] = dataclasses .field (default_factory = list )
627+ summary : Optional [str ] = ""
628+ affected_packages : List [AffectedPackageV2 ] = dataclasses .field (default_factory = list )
629+ references : List [ReferenceV2 ] = dataclasses .field (default_factory = list )
576630 patches : List [PatchData ] = dataclasses .field (default_factory = list )
577631 date_published : Optional [datetime .datetime ] = None
578632 weaknesses : List [int ] = dataclasses .field (default_factory = list )
@@ -581,46 +635,24 @@ class AdvisoryData:
581635 original_advisory_text : Optional [str ] = None
582636
583637 def __post_init__ (self ):
638+ if not self .advisory_id :
639+ raise ValueError ("advisory_id is required for AdvisoryDataV2" )
584640 if self .advisory_id and self .advisory_id in self .aliases :
585641 raise ValueError (
586642 f"advisory_id { self .advisory_id } should not be present in aliases { self .aliases } "
587643 )
588644 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
645+ self .summary = clean_summary (self .summary )
598646
599647 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- }
619648 return {
649+ "advisory_id" : self .advisory_id ,
620650 "aliases" : self .aliases ,
621651 "summary" : self .summary ,
622652 "affected_packages" : [pkg .to_dict () for pkg in self .affected_packages ],
623653 "references" : [ref .to_dict () for ref in self .references ],
654+ "patches" : [patch .to_dict () for patch in self .patches ],
655+ "severities" : [sev .to_dict () for sev in self .severities ],
624656 "date_published" : self .date_published .isoformat () if self .date_published else None ,
625657 "weaknesses" : self .weaknesses ,
626658 "url" : self .url if self .url else "" ,
@@ -629,31 +661,37 @@ def to_dict(self):
629661 @classmethod
630662 def from_dict (cls , advisory_data ):
631663 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- )
640664 transformed = {
641665 "aliases" : advisory_data ["aliases" ],
642666 "summary" : advisory_data ["summary" ],
643667 "affected_packages" : [
644- affected_package_cls .from_dict (pkg ) for pkg in affected_packages if pkg is not None
668+ AffectedPackageV2 .from_dict (pkg )
669+ for pkg in advisory_data ["affected_packages" ]
670+ if pkg is not None
645671 ],
646672 "patches" : [PatchData .from_dict (patch ) for patch in advisory_data .get ("patches" , [])],
647- "references" : [Reference .from_dict (ref ) for ref in advisory_data ["references" ]],
673+ "references" : [ReferenceV2 .from_dict (ref ) for ref in advisory_data ["references" ]],
648674 "date_published" : datetime .datetime .fromisoformat (date_published )
649675 if date_published
650676 else None ,
651677 "weaknesses" : advisory_data ["weaknesses" ],
678+ "severities" : [
679+ VulnerabilitySeverity .from_dict (sev ) for sev in advisory_data .get ("severities" , [])
680+ ],
652681 "url" : advisory_data .get ("url" ) or None ,
653682 }
654683 return cls (** transformed )
655684
656685
686+ def clean_summary (summary ):
687+ # https://nvd.nist.gov/vuln/detail/CVE-2013-4314
688+ # https://github.com/cms-dev/cms/issues/888#issuecomment-516977572
689+ summary = summary .strip ()
690+ if summary :
691+ summary = summary .replace ("\x00 " , "\uFFFD " )
692+ return summary
693+
694+
657695class NoLicenseError (Exception ):
658696 pass
659697
0 commit comments