Skip to content

Commit

Permalink
feat: add CPE to component (#138)
Browse files Browse the repository at this point in the history
* Added CPE to component

Setting CPE was missing for component, now it is possible to set CPE and output CPE for a component.

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Fixing problems with CPE addition

- Fixed styling errors
- Added reference to CPE Spec
- Adding CPE parameter as last parameter to not break arguments

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Again fixes for Style and CPE reference

Missing in the last commit

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Added CPE as argument before deprecated arguments

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Added testing for CPE addition and error fixing

- Added output tests for CPE in XML and JSON
- Fixes style error in components
- Fixes order for CPE output in XML (CPE has to come before PURL)

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Fixed output tests

CPE was still in the wrong position in one of the tests - fixed

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Fixed minor test fixtures issues

- cpe was still in wrong position in 1.2 JSON
- Indentation fixed in 1.4 JSON

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>

* Fixed missing comma in JSON 1.2 test file

Signed-off-by: Jens Lucius <jens.lucius@de.bosch.com>
  • Loading branch information
jblu42 authored Jan 24, 2022
1 parent dec63de commit 269ee15
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 1 deletion.
19 changes: 18 additions & 1 deletion cyclonedx/model/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def __init__(self, name: str, component_type: ComponentType = ComponentType.LIBR
copyright: Optional[str] = None, purl: Optional[PackageURL] = None,
external_references: Optional[List[ExternalReference]] = None,
properties: Optional[List[Property]] = None, release_notes: Optional[ReleaseNotes] = None,
cpe: Optional[str] = None,
# Deprecated parameters kept for backwards compatibility
namespace: Optional[str] = None, license_str: Optional[str] = None
) -> None:
Expand All @@ -124,6 +125,7 @@ def __init__(self, name: str, component_type: ComponentType = ComponentType.LIBR
self.licenses = licenses or []
self.copyright = copyright
self.purl = purl
self.cpe = cpe
self.external_references = external_references if external_references else []
self.properties = properties

Expand Down Expand Up @@ -392,6 +394,21 @@ def purl(self) -> Optional[PackageURL]:
def purl(self, purl: Optional[PackageURL]) -> None:
self._purl = purl

@property
def cpe(self) -> Optional[str]:
"""
Specifies a well-formed CPE name that conforms to the CPE 2.2 or 2.3 specification.
See https://nvd.nist.gov/products/cpe
Returns:
`str` if set else `None`
"""
return self._cpe

@cpe.setter
def cpe(self, cpe: Optional[str]) -> None:
self._cpe = cpe

@property
def external_references(self) -> List[ExternalReference]:
"""
Expand Down Expand Up @@ -492,7 +509,7 @@ def __hash__(self) -> int:
return hash((
self.author, self.bom_ref, self.copyright, self.description, str(self.external_references), self.group,
str(self.hashes), str(self.licenses), self.mime_type, self.name, self.properties, self.publisher, self.purl,
self.release_notes, self.scope, self.supplier, self.type, self.version
self.release_notes, self.scope, self.supplier, self.type, self.version, self.cpe
))

def __repr__(self) -> str:
Expand Down
4 changes: 4 additions & 0 deletions cyclonedx/output/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
else:
ElementTree.SubElement(licenses_e, 'expression').text = license.expression

# cpe
if component.cpe:
ElementTree.SubElement(component_element, 'cpe').text = component.cpe

# purl
if component.purl:
ElementTree.SubElement(component_element, 'purl').text = component.purl.to_string()
Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/bom_v1.0_setuptools_with_cpe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.0" version="1">
<components>
<component type="library">
<name>setuptools</name>
<version>50.3.2</version>
<cpe>cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*</cpe>
<purl>pkg:pypi/setuptools@50.3.2?extension=tar.gz</purl>
<modified>false</modified>
</component>
</components>
</bom>
12 changes: 12 additions & 0 deletions tests/fixtures/bom_v1.1_setuptools_with_cpe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.1" version="1"
serialNumber="urn:uuid:b409670b-e3e3-4691-b1ee-8eff057d74f5">
<components>
<component type="library" bom-ref="pkg:pypi/setuptools@50.3.2?extension=tar.gz">
<name>setuptools</name>
<version>50.3.2</version>
<cpe>cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*</cpe>
<purl>pkg:pypi/setuptools@50.3.2?extension=tar.gz</purl>
</component>
</components>
</bom>
28 changes: 28 additions & 0 deletions tests/fixtures/bom_v1.2_setuptools_with_cpe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.2a.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.2",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2021-09-01T10:50:42.051979+00:00",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-python-lib",
"version": "VERSION"
}
]
},
"components": [
{
"type": "library",
"bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz",
"author": "Test Author",
"name": "setuptools",
"version": "50.3.2",
"cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*",
"purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz"
}
]
}
21 changes: 21 additions & 0 deletions tests/fixtures/bom_v1.2_setuptools_with_cpe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1">
<metadata>
<timestamp>2021-09-01T10:50:42.051979+00:00</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cyclonedx-python-lib</name>
<version>VERSION</version>
</tool>
</tools>
</metadata>
<components>
<component type="library" bom-ref="pkg:pypi/setuptools@50.3.2?extension=tar.gz">
<name>setuptools</name>
<version>50.3.2</version>
<cpe>cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*</cpe>
<purl>pkg:pypi/setuptools@50.3.2?extension=tar.gz</purl>
</component>
</components>
</bom>
32 changes: 32 additions & 0 deletions tests/fixtures/bom_v1.3_setuptools_with_cpe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.3.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2021-09-01T10:50:42.051979+00:00",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-python-lib",
"version": "VERSION"
}
]
},
"components": [
{
"type": "library",
"name": "setuptools",
"version": "50.3.2",
"cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*",
"purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz",
"bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz",
"licenses": [
{
"expression": "MIT License"
}
]
}
]
}
21 changes: 21 additions & 0 deletions tests/fixtures/bom_v1.3_setuptools_with_cpe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" version="1">
<metadata>
<timestamp>2021-09-01T10:50:42.051979+00:00</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cyclonedx-python-lib</name>
<version>VERSION</version>
</tool>
</tools>
</metadata>
<components>
<component type="library" bom-ref="pkg:pypi/setuptools@50.3.2?extension=tar.gz">
<name>setuptools</name>
<version>50.3.2</version>
<cpe>cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*</cpe>
<purl>pkg:pypi/setuptools@50.3.2?extension=tar.gz</purl>
</component>
</components>
</bom>
61 changes: 61 additions & 0 deletions tests/fixtures/bom_v1.4_setuptools_with_cpe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2021-09-01T10:50:42.051979+00:00",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-python-lib",
"version": "VERSION",
"externalReferences": [
{
"type": "build-system",
"url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"
},
{
"type": "distribution",
"url": "https://pypi.org/project/cyclonedx-python-lib/"
},
{
"type": "documentation",
"url": "https://cyclonedx.github.io/cyclonedx-python-lib/"
},
{
"type": "issue-tracker",
"url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"
},
{
"type": "license",
"url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"
},
{
"type": "release-notes",
"url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"
},
{
"type": "vcs",
"url": "https://github.com/CycloneDX/cyclonedx-python-lib"
},
{
"type": "website",
"url": "https://cyclonedx.org"
}
]
}
]
},
"components": [
{
"type": "library",
"name": "setuptools",
"version": "50.3.2",
"cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*",
"purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz",
"bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz"
}
]
}
47 changes: 47 additions & 0 deletions tests/fixtures/bom_v1.4_setuptools_with_cpe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
<metadata>
<timestamp>2021-09-01T10:50:42.051979+00:00</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cyclonedx-python-lib</name>
<version>VERSION</version>
<externalReferences>
<reference type="build-system">
<url>https://github.com/CycloneDX/cyclonedx-python-lib/actions</url>
</reference>
<reference type="distribution">
<url>https://pypi.org/project/cyclonedx-python-lib/</url>
</reference>
<reference type="documentation">
<url>https://cyclonedx.github.io/cyclonedx-python-lib/</url>
</reference>
<reference type="issue-tracker">
<url>https://github.com/CycloneDX/cyclonedx-python-lib/issues</url>
</reference>
<reference type="license">
<url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE</url>
</reference>
<reference type="release-notes">
<url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md</url>
</reference>
<reference type="vcs">
<url>https://github.com/CycloneDX/cyclonedx-python-lib</url>
</reference>
<reference type="website">
<url>https://cyclonedx.org</url>
</reference>
</externalReferences>
</tool>
</tools>
</metadata>
<components>
<component type="library" bom-ref="pkg:pypi/setuptools@50.3.2?extension=tar.gz">
<name>setuptools</name>
<version>50.3.2</version>
<cpe>cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*</cpe>
<purl>pkg:pypi/setuptools@50.3.2?extension=tar.gz</purl>
</component>
</components>
</bom>
54 changes: 54 additions & 0 deletions tests/test_output_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,60 @@ def test_simple_bom_v1_2(self) -> None:
self.assertEqualJsonBom(expected_json.read(), outputter.output_as_string())
expected_json.close()

def test_simple_bom_v1_4_with_cpe(self) -> None:
bom = Bom()
c = Component(
name='setuptools', version='50.3.2', bom_ref='pkg:pypi/setuptools@50.3.2?extension=tar.gz',
cpe='cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*',
purl=PackageURL(
type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz'
)
)
bom.add_component(c)

outputter = get_instance(bom=bom, output_format=OutputFormat.JSON, schema_version=SchemaVersion.V1_4)
self.assertIsInstance(outputter, JsonV1Dot4)
with open(join(dirname(__file__), 'fixtures/bom_v1.4_setuptools_with_cpe.json')) as expected_json:
self.assertValidAgainstSchema(bom_json=outputter.output_as_string(), schema_version=SchemaVersion.V1_4)
self.assertEqualJsonBom(expected_json.read(), outputter.output_as_string())
expected_json.close()

def test_simple_bom_v1_3_with_cpe(self) -> None:
bom = Bom()
c = Component(
name='setuptools', version='50.3.2', bom_ref='pkg:pypi/setuptools@50.3.2?extension=tar.gz',
cpe='cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*',
purl=PackageURL(
type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz'
), license_str='MIT License'
)
bom.add_component(c)

outputter = get_instance(bom=bom, output_format=OutputFormat.JSON)
self.assertIsInstance(outputter, JsonV1Dot3)
with open(join(dirname(__file__), 'fixtures/bom_v1.3_setuptools_with_cpe.json')) as expected_json:
self.assertValidAgainstSchema(bom_json=outputter.output_as_string(), schema_version=SchemaVersion.V1_3)
self.assertEqualJsonBom(expected_json.read(), outputter.output_as_string())
expected_json.close()

def test_simple_bom_v1_2_with_cpe(self) -> None:
bom = Bom()
bom.add_component(
Component(
name='setuptools', version='50.3.2', bom_ref='pkg:pypi/setuptools@50.3.2?extension=tar.gz',
cpe='cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*',
purl=PackageURL(
type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz'
), author='Test Author'
)
)
outputter = get_instance(bom=bom, output_format=OutputFormat.JSON, schema_version=SchemaVersion.V1_2)
self.assertIsInstance(outputter, JsonV1Dot2)
with open(join(dirname(__file__), 'fixtures/bom_v1.2_setuptools_with_cpe.json')) as expected_json:
self.assertValidAgainstSchema(bom_json=outputter.output_as_string(), schema_version=SchemaVersion.V1_2)
self.assertEqualJsonBom(expected_json.read(), outputter.output_as_string())
expected_json.close()

def test_bom_v1_3_with_component_hashes(self) -> None:
bom = Bom()
c = Component(
Expand Down
Loading

0 comments on commit 269ee15

Please sign in to comment.