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,49 @@ 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
- 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 ))
42
60
43
61
return Xml .XML_VERSION_DECLARATION + ElementTree .tostring (bom , 'unicode' )
44
62
45
63
def _component_supports_bom_ref_attribute (self ) -> bool :
46
64
return True
47
65
48
66
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 )
51
77
52
78
def _get_component_as_xml_element (self , component : Component ) -> ElementTree .Element :
53
79
element_attributes = {'type' : component .get_type ().value }
@@ -91,6 +117,77 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele
91
117
92
118
return component_element
93
119
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
+
94
191
def _add_metadata (self , bom : ElementTree .Element ) -> ElementTree .Element :
95
192
metadata_e = ElementTree .SubElement (bom , 'metadata' )
96
193
ElementTree .SubElement (metadata_e , 'timestamp' ).text = self .get_bom ().get_metadata ().get_timestamp ().isoformat ()
0 commit comments