2222from . import BaseOutput
2323from .schema import BaseSchemaVersion , SchemaVersion1Dot0 , SchemaVersion1Dot1 , SchemaVersion1Dot2 , SchemaVersion1Dot3
2424from ..model .component import Component
25+ from ..model .vulnerability import Vulnerability , VulnerabilityRating
2526
2627
2728class Xml (BaseOutput , BaseSchemaVersion ):
@@ -30,24 +31,49 @@ class Xml(BaseOutput, BaseSchemaVersion):
3031 def get_target_namespace (self ) -> str :
3132 return 'http://cyclonedx.org/schema/bom/{}' .format (self .get_schema_version ())
3233
34+ @staticmethod
35+ def get_vulnerabilities_namespace () -> str :
36+ return 'http://cyclonedx.org/schema/ext/vulnerability/1.0'
37+
3338 def output_as_string (self ) -> str :
3439 bom = self ._get_bom_root_element ()
3540
3641 if self .bom_supports_metadata ():
3742 bom = self ._add_metadata (bom = bom )
3843
44+ if self .get_bom ().has_vulnerabilities ():
45+ ElementTree .register_namespace ('v' , Xml .get_vulnerabilities_namespace ())
46+
3947 components = ElementTree .SubElement (bom , 'components' )
48+ # if self.get_bom().has_vulnerabilities():
49+ # vulnerabilities = ElementTree.SubElement(bom, 'v:vulnerabilities')
50+
4051 for component in self .get_bom ().get_components ():
41- components .append (self ._get_component_as_xml_element (component = component ))
52+ component_element = self ._get_component_as_xml_element (component = component )
53+ components .append (component_element )
54+ if component .has_vulnerabilities () and self .component_supports_bom_ref ():
55+ # Vulnerabilities are only possible when bom-ref is supported by the main CycloneDX schema version
56+ vulnerabilities = ElementTree .SubElement (component_element , 'v:vulnerabilities' )
57+ for vulnerability in component .get_vulnerabilities ():
58+ vulnerabilities .append (self ._get_vulnerability_as_xml_element (bom_ref = component .get_purl (),
59+ vulnerability = vulnerability ))
4260
4361 return Xml .XML_VERSION_DECLARATION + ElementTree .tostring (bom , 'unicode' )
4462
4563 def _component_supports_bom_ref_attribute (self ) -> bool :
4664 return True
4765
4866 def _get_bom_root_element (self ) -> ElementTree .Element :
49- return ElementTree .Element ('bom' , {'xmlns' : self .get_target_namespace (), 'version' : '1' ,
50- 'serialNumber' : self .get_bom ().get_urn_uuid ()})
67+ root_attributes = {
68+ 'xmlns' : self .get_target_namespace (),
69+ 'version' : '1' ,
70+ 'serialNumber' : self .get_bom ().get_urn_uuid ()
71+ }
72+
73+ if self .get_bom ().has_vulnerabilities ():
74+ root_attributes ['xmlns:v' ] = Xml .get_vulnerabilities_namespace ()
75+
76+ return ElementTree .Element ('bom' , root_attributes )
5177
5278 def _get_component_as_xml_element (self , component : Component ) -> ElementTree .Element :
5379 element_attributes = {'type' : component .get_type ().value }
@@ -91,6 +117,77 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele
91117
92118 return component_element
93119
120+ @staticmethod
121+ def _get_vulnerability_as_xml_element (bom_ref : str , vulnerability : Vulnerability ) -> ElementTree .Element :
122+ vulnerability_element = ElementTree .Element ('v:vulnerability' , {
123+ 'ref' : bom_ref
124+ })
125+
126+ # id
127+ ElementTree .SubElement (vulnerability_element , 'v:id' ).text = vulnerability .get_id ()
128+
129+ # source
130+ if vulnerability .get_source_name ():
131+ source_element = ElementTree .SubElement (
132+ vulnerability_element , 'v:source' , attrib = {'name' : vulnerability .get_source_name ()}
133+ )
134+ if vulnerability .get_source_url ():
135+ ElementTree .SubElement (source_element , 'v:url' ).text = vulnerability .get_source_url ().geturl ()
136+
137+ # ratings
138+ if vulnerability .has_ratings ():
139+ ratings_element = ElementTree .SubElement (vulnerability_element , 'v:ratings' )
140+ rating : VulnerabilityRating
141+ for rating in vulnerability .get_ratings ():
142+ rating_element = ElementTree .SubElement (ratings_element , 'v:rating' )
143+
144+ # rating.score
145+ if rating .has_score ():
146+ score_element = ElementTree .SubElement (rating_element , 'v:score' )
147+ if rating .get_base_score ():
148+ ElementTree .SubElement (score_element , 'v:base' ).text = str (rating .get_base_score ())
149+ if rating .get_impact_score ():
150+ ElementTree .SubElement (score_element , 'v:impact' ).text = str (rating .get_impact_score ())
151+ if rating .get_exploitability_score ():
152+ ElementTree .SubElement (score_element ,
153+ 'v:exploitability' ).text = str (rating .get_exploitability_score ())
154+
155+ # rating.severity
156+ if rating .get_severity ():
157+ ElementTree .SubElement (rating_element , 'v:severity' ).text = rating .get_severity ().value
158+
159+ # rating.severity
160+ if rating .get_method ():
161+ ElementTree .SubElement (rating_element , 'v:method' ).text = rating .get_method ().value
162+
163+ # rating.vector
164+ if rating .get_vector ():
165+ ElementTree .SubElement (rating_element , 'v:vector' ).text = rating .get_vector ()
166+
167+ # cwes
168+ if vulnerability .has_cwes ():
169+ cwes_element = ElementTree .SubElement (vulnerability_element , 'v:cwes' )
170+ for cwe in vulnerability .get_cwes ():
171+ ElementTree .SubElement (cwes_element , 'v:cwe' ).text = str (cwe )
172+
173+ # description
174+ if vulnerability .get_description ():
175+ ElementTree .SubElement (vulnerability_element , 'v:description' ).text = vulnerability .get_description ()
176+
177+ # recommendations
178+ if vulnerability .has_recommendations ():
179+ recommendations_element = ElementTree .SubElement (vulnerability_element , 'v:recommendations' )
180+ for recommendation in vulnerability .get_recommendations ():
181+ ElementTree .SubElement (recommendations_element , 'v:recommendation' ).text = recommendation
182+
183+ # advisories
184+ if vulnerability .has_advisories ():
185+ advisories_element = ElementTree .SubElement (vulnerability_element , 'v:advisories' )
186+ for advisory in vulnerability .get_advisories ():
187+ ElementTree .SubElement (advisories_element , 'v:advisory' ).text = advisory
188+
189+ return vulnerability_element
190+
94191 def _add_metadata (self , bom : ElementTree .Element ) -> ElementTree .Element :
95192 metadata_e = ElementTree .SubElement (bom , 'metadata' )
96193 ElementTree .SubElement (metadata_e , 'timestamp' ).text = self .get_bom ().get_metadata ().get_timestamp ().isoformat ()
0 commit comments