|
24 | 24 | from . import BaseOutput, SchemaVersion
|
25 | 25 | from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3, \
|
26 | 26 | SchemaVersion1Dot4
|
27 |
| -from ..model import ExternalReference, HashType, OrganizationalEntity, OrganizationalContact, Tool |
| 27 | +from ..model import ExternalReference, HashType, OrganizationalEntity, OrganizationalContact, Property, Tool |
28 | 28 | from ..model.bom import Bom
|
29 | 29 | from ..model.component import Component
|
| 30 | +from ..model.release_note import ReleaseNotes |
| 31 | +from ..model.service import Service |
30 | 32 | from ..model.vulnerability import Vulnerability, VulnerabilityRating, VulnerabilitySource, BomTargetVersionRange
|
31 | 33 |
|
32 | 34 |
|
@@ -75,13 +77,19 @@ def generate(self, force_regeneration: bool = False) -> None:
|
75 | 77 | elif component.has_vulnerabilities():
|
76 | 78 | has_vulnerabilities = True
|
77 | 79 |
|
78 |
| - if self.bom_supports_vulnerabilities() and has_vulnerabilities: |
79 |
| - vulnerabilities_element = ElementTree.SubElement(self._root_bom_element, 'vulnerabilities') |
80 |
| - for component in cast(List[Component], self.get_bom().components): |
81 |
| - for vulnerability in component.get_vulnerabilities(): |
82 |
| - vulnerabilities_element.append( |
83 |
| - self._get_vulnerability_as_xml_element_post_1_4(vulnerability=vulnerability) |
84 |
| - ) |
| 80 | + if self.bom_supports_services(): |
| 81 | + if self.get_bom().services: |
| 82 | + services_element = ElementTree.SubElement(self._root_bom_element, 'services') |
| 83 | + for service in cast(List[Service], self.get_bom().services): |
| 84 | + services_element.append(self._add_service_element(service=service)) |
| 85 | + |
| 86 | + if self.bom_supports_vulnerabilities() and has_vulnerabilities: |
| 87 | + vulnerabilities_element = ElementTree.SubElement(self._root_bom_element, 'vulnerabilities') |
| 88 | + for component in cast(List[Component], self.get_bom().components): |
| 89 | + for vulnerability in component.get_vulnerabilities(): |
| 90 | + vulnerabilities_element.append( |
| 91 | + self._get_vulnerability_as_xml_element_post_1_4(vulnerability=vulnerability) |
| 92 | + ) |
85 | 93 |
|
86 | 94 | self.generated = True
|
87 | 95 |
|
@@ -213,74 +221,172 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
|
213 | 221 |
|
214 | 222 | # releaseNotes
|
215 | 223 | if self.component_supports_release_notes() and component.release_notes:
|
216 |
| - release_notes_e = ElementTree.SubElement(component_element, 'releaseNotes') |
217 |
| - release_notes = component.release_notes |
218 |
| - |
219 |
| - ElementTree.SubElement(release_notes_e, 'type').text = release_notes.type |
220 |
| - if release_notes.title: |
221 |
| - ElementTree.SubElement(release_notes_e, 'title').text = release_notes.title |
222 |
| - if release_notes.featured_image: |
223 |
| - ElementTree.SubElement(release_notes_e, |
224 |
| - 'featuredImage').text = str(release_notes.featured_image) |
225 |
| - if release_notes.social_image: |
226 |
| - ElementTree.SubElement(release_notes_e, |
227 |
| - 'socialImage').text = str(release_notes.social_image) |
228 |
| - if release_notes.description: |
229 |
| - ElementTree.SubElement(release_notes_e, |
230 |
| - 'description').text = release_notes.description |
231 |
| - if release_notes.timestamp: |
232 |
| - ElementTree.SubElement(release_notes_e, 'timestamp').text = release_notes.timestamp.isoformat() |
233 |
| - if release_notes.aliases: |
234 |
| - release_notes_aliases_e = ElementTree.SubElement(release_notes_e, 'aliases') |
235 |
| - for alias in release_notes.aliases: |
236 |
| - ElementTree.SubElement(release_notes_aliases_e, 'alias').text = alias |
237 |
| - if release_notes.tags: |
238 |
| - release_notes_tags_e = ElementTree.SubElement(release_notes_e, 'tags') |
239 |
| - for tag in release_notes.tags: |
240 |
| - ElementTree.SubElement(release_notes_tags_e, 'tag').text = tag |
241 |
| - if release_notes.resolves: |
242 |
| - release_notes_resolves_e = ElementTree.SubElement(release_notes_e, 'resolves') |
243 |
| - for issue in release_notes.resolves: |
244 |
| - issue_e = ElementTree.SubElement( |
245 |
| - release_notes_resolves_e, 'issue', {'type': issue.get_classification().value} |
246 |
| - ) |
247 |
| - if issue.get_id(): |
248 |
| - ElementTree.SubElement(issue_e, 'id').text = issue.get_id() |
249 |
| - if issue.get_name(): |
250 |
| - ElementTree.SubElement(issue_e, 'name').text = issue.get_name() |
251 |
| - if issue.get_description(): |
252 |
| - ElementTree.SubElement(issue_e, 'description').text = issue.get_description() |
253 |
| - if issue.source: |
254 |
| - issue_source_e = ElementTree.SubElement(issue_e, 'source') |
255 |
| - if issue.source.name: |
256 |
| - ElementTree.SubElement(issue_source_e, 'name').text = issue.source.name |
257 |
| - if issue.source.url: |
258 |
| - ElementTree.SubElement(issue_source_e, 'url').text = str(issue.source.url) |
259 |
| - if issue.get_references(): |
260 |
| - issue_references_e = ElementTree.SubElement(issue_e, 'references') |
261 |
| - for reference in issue.get_references(): |
262 |
| - ElementTree.SubElement(issue_references_e, 'url').text = str(reference) |
263 |
| - if release_notes.notes: |
264 |
| - release_notes_notes_e = ElementTree.SubElement(release_notes_e, 'notes') |
265 |
| - for note in release_notes.notes: |
266 |
| - note_e = ElementTree.SubElement(release_notes_notes_e, 'note') |
267 |
| - if note.locale: |
268 |
| - ElementTree.SubElement(note_e, 'locale').text = note.locale |
269 |
| - text_attrs = {} |
270 |
| - if note.text.content_type: |
271 |
| - text_attrs['content-type'] = note.text.content_type |
272 |
| - if note.text.encoding: |
273 |
| - text_attrs['encoding'] = note.text.encoding.value |
274 |
| - ElementTree.SubElement(note_e, 'text', text_attrs).text = note.text.content |
275 |
| - if release_notes.properties: |
276 |
| - release_notes_properties_e = ElementTree.SubElement(release_notes_e, 'properties') |
277 |
| - for prop in release_notes.properties: |
278 |
| - ElementTree.SubElement( |
279 |
| - release_notes_properties_e, 'property', {'name': prop.get_name()} |
280 |
| - ).text = prop.get_value() |
| 224 | + Xml._add_release_notes_element(release_notes=component.release_notes, parent_element=component_element) |
281 | 225 |
|
282 | 226 | return component_element
|
283 | 227 |
|
| 228 | + @staticmethod |
| 229 | + def _add_release_notes_element(release_notes: ReleaseNotes, parent_element: ElementTree.Element) -> None: |
| 230 | + release_notes_e = ElementTree.SubElement(parent_element, 'releaseNotes') |
| 231 | + |
| 232 | + ElementTree.SubElement(release_notes_e, 'type').text = release_notes.type |
| 233 | + if release_notes.title: |
| 234 | + ElementTree.SubElement(release_notes_e, 'title').text = release_notes.title |
| 235 | + if release_notes.featured_image: |
| 236 | + ElementTree.SubElement(release_notes_e, |
| 237 | + 'featuredImage').text = str(release_notes.featured_image) |
| 238 | + if release_notes.social_image: |
| 239 | + ElementTree.SubElement(release_notes_e, |
| 240 | + 'socialImage').text = str(release_notes.social_image) |
| 241 | + if release_notes.description: |
| 242 | + ElementTree.SubElement(release_notes_e, |
| 243 | + 'description').text = release_notes.description |
| 244 | + if release_notes.timestamp: |
| 245 | + ElementTree.SubElement(release_notes_e, 'timestamp').text = release_notes.timestamp.isoformat() |
| 246 | + if release_notes.aliases: |
| 247 | + release_notes_aliases_e = ElementTree.SubElement(release_notes_e, 'aliases') |
| 248 | + for alias in release_notes.aliases: |
| 249 | + ElementTree.SubElement(release_notes_aliases_e, 'alias').text = alias |
| 250 | + if release_notes.tags: |
| 251 | + release_notes_tags_e = ElementTree.SubElement(release_notes_e, 'tags') |
| 252 | + for tag in release_notes.tags: |
| 253 | + ElementTree.SubElement(release_notes_tags_e, 'tag').text = tag |
| 254 | + if release_notes.resolves: |
| 255 | + release_notes_resolves_e = ElementTree.SubElement(release_notes_e, 'resolves') |
| 256 | + for issue in release_notes.resolves: |
| 257 | + issue_e = ElementTree.SubElement( |
| 258 | + release_notes_resolves_e, 'issue', {'type': issue.get_classification().value} |
| 259 | + ) |
| 260 | + if issue.get_id(): |
| 261 | + ElementTree.SubElement(issue_e, 'id').text = issue.get_id() |
| 262 | + if issue.get_name(): |
| 263 | + ElementTree.SubElement(issue_e, 'name').text = issue.get_name() |
| 264 | + if issue.get_description(): |
| 265 | + ElementTree.SubElement(issue_e, 'description').text = issue.get_description() |
| 266 | + if issue.source: |
| 267 | + issue_source_e = ElementTree.SubElement(issue_e, 'source') |
| 268 | + if issue.source.name: |
| 269 | + ElementTree.SubElement(issue_source_e, 'name').text = issue.source.name |
| 270 | + if issue.source.url: |
| 271 | + ElementTree.SubElement(issue_source_e, 'url').text = str(issue.source.url) |
| 272 | + if issue.get_references(): |
| 273 | + issue_references_e = ElementTree.SubElement(issue_e, 'references') |
| 274 | + for reference in issue.get_references(): |
| 275 | + ElementTree.SubElement(issue_references_e, 'url').text = str(reference) |
| 276 | + if release_notes.notes: |
| 277 | + release_notes_notes_e = ElementTree.SubElement(release_notes_e, 'notes') |
| 278 | + for note in release_notes.notes: |
| 279 | + note_e = ElementTree.SubElement(release_notes_notes_e, 'note') |
| 280 | + if note.locale: |
| 281 | + ElementTree.SubElement(note_e, 'locale').text = note.locale |
| 282 | + text_attrs = {} |
| 283 | + if note.text.content_type: |
| 284 | + text_attrs['content-type'] = note.text.content_type |
| 285 | + if note.text.encoding: |
| 286 | + text_attrs['encoding'] = note.text.encoding.value |
| 287 | + ElementTree.SubElement(note_e, 'text', text_attrs).text = note.text.content |
| 288 | + if release_notes.properties: |
| 289 | + Xml._add_properties_element(properties=release_notes.properties, parent_element=release_notes_e) |
| 290 | + |
| 291 | + @staticmethod |
| 292 | + def _add_properties_element(properties: List[Property], parent_element: ElementTree.Element) -> None: |
| 293 | + properties_e = ElementTree.SubElement(parent_element, 'properties') |
| 294 | + for property in properties: |
| 295 | + ElementTree.SubElement( |
| 296 | + properties_e, 'property', {'name': property.get_name()} |
| 297 | + ).text = property.get_value() |
| 298 | + |
| 299 | + def _add_service_element(self, service: Service) -> ElementTree.Element: |
| 300 | + element_attributes = {} |
| 301 | + if service.bom_ref: |
| 302 | + element_attributes['bom-ref'] = service.bom_ref |
| 303 | + |
| 304 | + service_element = ElementTree.Element('service', element_attributes) |
| 305 | + |
| 306 | + # provider |
| 307 | + if service.provider: |
| 308 | + self._add_organizational_entity( |
| 309 | + parent_element=service_element, organization=service.provider, tag_name='provider' |
| 310 | + ) |
| 311 | + |
| 312 | + # group |
| 313 | + if service.group: |
| 314 | + ElementTree.SubElement(service_element, 'group').text = service.group |
| 315 | + |
| 316 | + # name |
| 317 | + ElementTree.SubElement(service_element, 'name').text = service.name |
| 318 | + |
| 319 | + # version |
| 320 | + if service.version: |
| 321 | + ElementTree.SubElement(service_element, 'version').text = service.version |
| 322 | + |
| 323 | + # description |
| 324 | + if service.description: |
| 325 | + ElementTree.SubElement(service_element, 'description').text = service.description |
| 326 | + |
| 327 | + # endpoints |
| 328 | + if service.endpoints: |
| 329 | + endpoints_e = ElementTree.SubElement(service_element, 'endpoints') |
| 330 | + for endpoint in service.endpoints: |
| 331 | + ElementTree.SubElement(endpoints_e, 'endpoint').text = str(endpoint) |
| 332 | + |
| 333 | + # authenticated |
| 334 | + if isinstance(service.authenticated, bool): |
| 335 | + ElementTree.SubElement(service_element, 'authenticated').text = str(service.authenticated).lower() |
| 336 | + |
| 337 | + # x-trust-boundary |
| 338 | + if isinstance(service.x_trust_boundary, bool): |
| 339 | + ElementTree.SubElement(service_element, 'x-trust-boundary').text = str(service.x_trust_boundary).lower() |
| 340 | + |
| 341 | + # data |
| 342 | + if service.data: |
| 343 | + data_e = ElementTree.SubElement(service_element, 'data') |
| 344 | + for data in service.data: |
| 345 | + ElementTree.SubElement(data_e, 'classification', {'flow': data.flow.value}).text = data.classification |
| 346 | + |
| 347 | + # licenses |
| 348 | + if service.licenses: |
| 349 | + licenses_e = ElementTree.SubElement(service_element, 'licenses') |
| 350 | + for license in service.licenses: |
| 351 | + if license.license: |
| 352 | + license_e = ElementTree.SubElement(licenses_e, 'license') |
| 353 | + if license.license.id: |
| 354 | + ElementTree.SubElement(license_e, 'id').text = license.license.id |
| 355 | + elif license.license.name: |
| 356 | + ElementTree.SubElement(license_e, 'name').text = license.license.name |
| 357 | + if license.license.text: |
| 358 | + license_text_e_attrs = {} |
| 359 | + if license.license.text.content_type: |
| 360 | + license_text_e_attrs['content-type'] = license.license.text.content_type |
| 361 | + if license.license.text.encoding: |
| 362 | + license_text_e_attrs['encoding'] = license.license.text.encoding.value |
| 363 | + ElementTree.SubElement(license_e, 'text', |
| 364 | + license_text_e_attrs).text = license.license.text.content |
| 365 | + |
| 366 | + ElementTree.SubElement(license_e, 'text').text = license.license.id |
| 367 | + else: |
| 368 | + ElementTree.SubElement(licenses_e, 'expression').text = license.expression |
| 369 | + |
| 370 | + # externalReferences |
| 371 | + if service.external_references: |
| 372 | + self._add_external_references_to_element(ext_refs=service.external_references, element=service_element) |
| 373 | + |
| 374 | + # properties |
| 375 | + if service.properties and self.services_supports_properties(): |
| 376 | + Xml._add_properties_element(properties=service.properties, parent_element=service_element) |
| 377 | + |
| 378 | + # services |
| 379 | + if service.services: |
| 380 | + services_element = ElementTree.SubElement(service_element, 'services') |
| 381 | + for sub_service in service.services: |
| 382 | + services_element.append(self._add_service_element(service=sub_service)) |
| 383 | + |
| 384 | + # releaseNotes |
| 385 | + if service.release_notes and self.services_supports_release_notes(): |
| 386 | + Xml._add_release_notes_element(release_notes=service.release_notes, parent_element=service_element) |
| 387 | + |
| 388 | + return service_element |
| 389 | + |
284 | 390 | def _get_vulnerability_as_xml_element_post_1_4(self, vulnerability: Vulnerability) -> ElementTree.Element:
|
285 | 391 | vulnerability_element = ElementTree.Element(
|
286 | 392 | 'vulnerability',
|
|
0 commit comments