22
22
from . import BaseOutput
23
23
from .schema import BaseSchemaVersion , SchemaVersion1Dot0 , SchemaVersion1Dot1 , SchemaVersion1Dot2 , SchemaVersion1Dot3
24
24
from ..model .component import Component
25
+ from ..model .vulnerability import Vulnerability , VulnerabilityRating
25
26
26
27
27
28
class Xml (BaseOutput , BaseSchemaVersion ):
@@ -30,24 +31,47 @@ class Xml(BaseOutput, BaseSchemaVersion):
30
31
def get_target_namespace (self ) -> str :
31
32
return 'http://cyclonedx.org/schema/bom/{}' .format (self .get_schema_version ())
32
33
34
+ @staticmethod
35
+ def get_vulnerabilities_namespace () -> str :
36
+ return 'http://cyclonedx.org/schema/ext/vulnerability/1.0'
37
+
33
38
def output_as_string (self ) -> str :
34
39
bom = self ._get_bom_root_element ()
35
40
36
41
if self .bom_supports_metadata ():
37
42
bom = self ._add_metadata (bom = bom )
38
43
44
+ if self .get_bom ().has_vulnerabilities ():
45
+ ElementTree .register_namespace ('v' , Xml .get_vulnerabilities_namespace ())
46
+
39
47
components = ElementTree .SubElement (bom , 'components' )
48
+ if self .get_bom ().has_vulnerabilities ():
49
+ vulnerabilities = ElementTree .SubElement (bom , 'v:vulnerabilities' )
50
+
40
51
for component in self .get_bom ().get_components ():
41
52
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 ))
42
58
43
59
return Xml .XML_VERSION_DECLARATION + ElementTree .tostring (bom , 'unicode' )
44
60
45
61
def _component_supports_bom_ref_attribute (self ) -> bool :
46
62
return True
47
63
48
64
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 )
51
75
52
76
def _get_component_as_xml_element (self , component : Component ) -> ElementTree .Element :
53
77
element_attributes = {'type' : component .get_type ().value }
@@ -91,6 +115,77 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele
91
115
92
116
return component_element
93
117
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
+
94
189
def _add_metadata (self , bom : ElementTree .Element ) -> ElementTree .Element :
95
190
metadata_e = ElementTree .SubElement (bom , 'metadata' )
96
191
ElementTree .SubElement (metadata_e , 'timestamp' ).text = self .get_bom ().get_metadata ().get_timestamp ().isoformat ()
0 commit comments