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,47 @@ 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 ():
4152 components .append (self ._get_component_as_xml_element (component = component ))
53+ if component .has_vulnerabilities () and self .component_supports_bom_ref ():
54+ # Vulnerabilities are only possible when bom-ref is supported by the main CycloneDX schema version
55+ for vulnerability in component .get_vulnerabilities ():
56+ vulnerabilities .append (self ._get_vulnerability_as_xml_element (bom_ref = component .get_purl (),
57+ vulnerability = vulnerability ))
4258
4359 return Xml .XML_VERSION_DECLARATION + ElementTree .tostring (bom , 'unicode' )
4460
4561 def _component_supports_bom_ref_attribute (self ) -> bool :
4662 return True
4763
4864 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 ()})
65+ root_attributes = {
66+ 'xmlns' : self .get_target_namespace (),
67+ 'version' : '1' ,
68+ 'serialNumber' : self .get_bom ().get_urn_uuid ()
69+ }
70+
71+ if self .get_bom ().has_vulnerabilities ():
72+ root_attributes ['xmlns:v' ] = Xml .get_vulnerabilities_namespace ()
73+
74+ return ElementTree .Element ('bom' , root_attributes )
5175
5276 def _get_component_as_xml_element (self , component : Component ) -> ElementTree .Element :
5377 element_attributes = {'type' : component .get_type ().value }
@@ -91,6 +115,77 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele
91115
92116 return component_element
93117
118+ @staticmethod
119+ def _get_vulnerability_as_xml_element (bom_ref : str , vulnerability : Vulnerability ) -> ElementTree .Element :
120+ vulnerability_element = ElementTree .Element ('v:vulnerability' , {
121+ 'ref' : bom_ref
122+ })
123+
124+ # id
125+ ElementTree .SubElement (vulnerability_element , 'v:id' ).text = vulnerability .get_id ()
126+
127+ # source
128+ if vulnerability .get_source_name ():
129+ source_element = ElementTree .SubElement (
130+ vulnerability_element , 'v:source' , attrib = {'name' : vulnerability .get_source_name ()}
131+ )
132+ if vulnerability .get_source_url ():
133+ ElementTree .SubElement (source_element , 'v:url' ).text = vulnerability .get_source_url ().geturl ()
134+
135+ # ratings
136+ if vulnerability .has_ratings ():
137+ ratings_element = ElementTree .SubElement (vulnerability_element , 'v:ratings' )
138+ rating : VulnerabilityRating
139+ for rating in vulnerability .get_ratings ():
140+ rating_element = ElementTree .SubElement (ratings_element , 'v:rating' )
141+
142+ # rating.score
143+ if rating .has_score ():
144+ score_element = ElementTree .SubElement (rating_element , 'v:score' )
145+ if rating .get_base_score ():
146+ ElementTree .SubElement (score_element , 'v:base' ).text = str (rating .get_base_score ())
147+ if rating .get_impact_score ():
148+ ElementTree .SubElement (score_element , 'v:impact' ).text = str (rating .get_impact_score ())
149+ if rating .get_exploitability_score ():
150+ ElementTree .SubElement (score_element ,
151+ 'v:exploitability' ).text = str (rating .get_exploitability_score ())
152+
153+ # rating.severity
154+ if rating .get_severity ():
155+ ElementTree .SubElement (rating_element , 'v:severity' ).text = rating .get_severity ().value
156+
157+ # rating.severity
158+ if rating .get_method ():
159+ ElementTree .SubElement (rating_element , 'v:method' ).text = rating .get_method ().value
160+
161+ # rating.vector
162+ if rating .get_vector ():
163+ ElementTree .SubElement (rating_element , 'v:vector' ).text = rating .get_vector ()
164+
165+ # cwes
166+ if vulnerability .has_cwes ():
167+ cwes_element = ElementTree .SubElement (vulnerability_element , 'v:cwes' )
168+ for cwe in vulnerability .get_cwes ():
169+ ElementTree .SubElement (cwes_element , 'v:cwe' ).text = str (cwe )
170+
171+ # description
172+ if vulnerability .get_description ():
173+ ElementTree .SubElement (vulnerability_element , 'v:description' ).text = vulnerability .get_description ()
174+
175+ # recommendations
176+ if vulnerability .has_recommendations ():
177+ recommendations_element = ElementTree .SubElement (vulnerability_element , 'v:recommendations' )
178+ for recommendation in vulnerability .get_recommendations ():
179+ ElementTree .SubElement (recommendations_element , 'v:recommendation' ).text = recommendation
180+
181+ # advisories
182+ if vulnerability .has_advisories ():
183+ advisories_element = ElementTree .SubElement (vulnerability_element , 'v:advisories' )
184+ for advisory in vulnerability .get_advisories ():
185+ ElementTree .SubElement (advisories_element , 'v:advisory' ).text = advisory
186+
187+ return vulnerability_element
188+
94189 def _add_metadata (self , bom : ElementTree .Element ) -> ElementTree .Element :
95190 metadata_e = ElementTree .SubElement (bom , 'metadata' )
96191 ElementTree .SubElement (metadata_e , 'timestamp' ).text = self .get_bom ().get_metadata ().get_timestamp ().isoformat ()
0 commit comments