--- a/src/eric7/CycloneDXInterface/CycloneDXUtilities.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/CycloneDXInterface/CycloneDXUtilities.py Wed Jul 13 14:55:47 2022 +0200 @@ -18,14 +18,21 @@ from packageurl import PackageURL from cyclonedx.model import ( - ExternalReference, ExternalReferenceType, LicenseChoice, - OrganizationalContact, OrganizationalEntity, Tool, XsUri + ExternalReference, + ExternalReferenceType, + LicenseChoice, + OrganizationalContact, + OrganizationalEntity, + Tool, + XsUri, ) from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.model.vulnerability import Vulnerability, VulnerabilitySource from cyclonedx.output import ( - OutputFormat, SchemaVersion, get_instance as get_output_instance + OutputFormat, + SchemaVersion, + get_instance as get_output_instance, ) from cyclonedx.parser import BaseParser @@ -33,24 +40,23 @@ from cyclonedx_py.parser.poetry import PoetryFileParser from cyclonedx_py.parser.requirements import RequirementsFileParser -from PipInterface.PipVulnerabilityChecker import ( - Package, VulnerabilityCheckError -) +from PipInterface.PipVulnerabilityChecker import Package, VulnerabilityCheckError class CycloneDXEnvironmentParser(BaseParser): """ Class implementing a parser to get package data for a named environment. """ + def __init__(self, venvName): """ Constructor - + @param venvName name of the virtual environment @type str """ super().__init__() - + pip = ericApp().getObject("Pip") packages = pip.getLicenses(venvName) for package in packages: @@ -60,40 +66,44 @@ author=package["Author"], description=package["Description"], purl=PackageURL( - type='pypi', - name=package["Name"], - version=package["Version"] - ) + type="pypi", name=package["Name"], version=package["Version"] + ), ) for lic in package["License"].split(";"): - comp.licenses.add( - LicenseChoice(license_expression=lic.strip()) - ) - + comp.licenses.add(LicenseChoice(license_expression=lic.strip())) + self._components.append(comp) def createCycloneDXFile(venvName): """ Function to create a CyccloneDX SBOM file. - + @param venvName name of the virtual environment @type str @exception RuntimeError raised to indicate illegal creation parameters """ from .CycloneDXConfigDialog import CycloneDXConfigDialog + dlg = CycloneDXConfigDialog(venvName) if dlg.exec() == QDialog.DialogCode.Accepted: - (inputSource, inputFile, fileFormat, schemaVersion, sbomFile, - withVulnerabilities, withDependencies, metadataDict) = dlg.getData() - + ( + inputSource, + inputFile, + fileFormat, + schemaVersion, + sbomFile, + withVulnerabilities, + withDependencies, + metadataDict, + ) = dlg.getData() + # check error conditions first - if inputSource not in ("environment", "pipenv", "poetry", - "requirements"): + if inputSource not in ("environment", "pipenv", "poetry", "requirements"): raise RuntimeError("Unsupported input source given.") if fileFormat not in ("XML", "JSON"): raise RuntimeError("Unsupported SBOM file format given.") - + if inputSource == "environment": parser = CycloneDXEnvironmentParser(venvName) else: @@ -102,106 +112,108 @@ EricMessageBox.warning( None, QCoreApplication.translate( - "CycloneDX", "CycloneDX - SBOM Creation"), + "CycloneDX", "CycloneDX - SBOM Creation" + ), QCoreApplication.translate( "CycloneDX", "<p>The configured input file <b>{0}</b> does not" - " exist. Aborting...</p>" - ).format(inputFile) + " exist. Aborting...</p>", + ).format(inputFile), ) return - + if inputSource == "pipenv": parser = PipEnvFileParser(pipenv_lock_filename=inputFile) elif inputSource == "poetry": parser = PoetryFileParser(poetry_lock_filename=inputFile) elif inputSource == "requirements": parser = RequirementsFileParser(requirements_file=inputFile) - + if withVulnerabilities: addCycloneDXVulnerabilities(parser) - + if withDependencies: addCycloneDXDependencies(parser, venvName) - + if fileFormat == "XML": outputFormat = OutputFormat.XML elif fileFormat == "JSON": outputFormat = OutputFormat.JSON - + if parser.has_warnings(): - excludedList = ["<li>{0}</li>".format(warning.get_item()) - for warning in parser.get_warnings()] + excludedList = [ + "<li>{0}</li>".format(warning.get_item()) + for warning in parser.get_warnings() + ] EricMessageBox.warning( None, - QCoreApplication.translate( - "CycloneDX", "CycloneDX - SBOM Creation"), + QCoreApplication.translate("CycloneDX", "CycloneDX - SBOM Creation"), QCoreApplication.translate( "CycloneDX", "<p>Some of the dependencies do not have pinned version" " numbers.<ul>{0}</ul>The above listed packages will NOT" " be included in the generated CycloneDX SBOM file as" - " version is a mandatory field.</p>" - ).format("".join(excludedList)) + " version is a mandatory field.</p>", + ).format("".join(excludedList)), ) - + bom = Bom.from_parser(parser=parser) _amendMetaData(bom.metadata, metadataDict) output = get_output_instance( bom=bom, output_format=outputFormat, - schema_version=SchemaVersion['V{0}'.format( - schemaVersion.replace('.', '_') - )] + schema_version=SchemaVersion[ + "V{0}".format(schemaVersion.replace(".", "_")) + ], ) output.output_to_file(filename=sbomFile, allow_overwrite=True) - + EricMessageBox.information( None, - QCoreApplication.translate( - "CycloneDX", "CycloneDX - SBOM Creation"), + QCoreApplication.translate("CycloneDX", "CycloneDX - SBOM Creation"), QCoreApplication.translate( - "CycloneDX", - "<p>The SBOM data was written to file <b>{0}</b>.</p>" - ).format(sbomFile) + "CycloneDX", "<p>The SBOM data was written to file <b>{0}</b>.</p>" + ).format(sbomFile), ) def addCycloneDXVulnerabilities(parser): """ Function to add vulnerability data to the list of created components. - + @param parser reference to the parser object containing the list of components @type BaseParser """ components = parser.get_components() - + packages = [ Package(name=component.name, version=component.version) for component in components ] - + pip = ericApp().getObject("Pip") error, vulnerabilities = pip.getVulnerabilityChecker().check(packages) - + if error == VulnerabilityCheckError.OK: for package in vulnerabilities: component = findCyccloneDXComponent(components, package) if component: for vuln in vulnerabilities[package]: - component.add_vulnerability(Vulnerability( - id=vuln.cve, - description=vuln.advisory, - recommendation="upgrade required", - source=VulnerabilitySource(name="pyup.io") - )) + component.add_vulnerability( + Vulnerability( + id=vuln.cve, + description=vuln.advisory, + recommendation="upgrade required", + source=VulnerabilitySource(name="pyup.io"), + ) + ) def addCycloneDXDependencies(parser, venvName): """ Function to add dependency data to the list of created components. - + @param parser reference to the parser object containing the list of components @type BaseParser @@ -209,7 +221,7 @@ @type str """ components = parser.get_components() - + pip = ericApp().getObject("Pip") dependencies = pip.getDependencyTree(venvName) for dependency in dependencies: @@ -219,7 +231,7 @@ def _addCycloneDXDependency(dependency, components): """ Function to add a dependency to the given list of components. - + @param dependency dependency to be added @type dict @param components list of components @@ -229,8 +241,7 @@ if component is not None: bomRefs = component.dependencies for dep in dependency["dependencies"]: - depComponent = findCyccloneDXComponent( - components, dep["package_name"]) + depComponent = findCyccloneDXComponent(components, dep["package_name"]) if depComponent is not None: bomRefs.add(depComponent.bom_ref) # recursively add sub-dependencies @@ -241,7 +252,7 @@ def findCyccloneDXComponent(components, name): """ Function to find a component in a given list of components. - + @param components list of components to scan @type list of Component @param name name of the component to search for @@ -252,19 +263,19 @@ for component in components: if component.name == name: return component - + return None def _amendMetaData(bomMetaData, metadataDict): """ Function to amend the SBOM meta data according the given data. - + The modifications done are: <ul> <li>add eric7 to the tools</li> </ul> - + @param bomMetaData reference to the SBOM meta data object @type BomMetaData @param metadataDict dictionary containing additional meta data @@ -275,80 +286,74 @@ # add a Tool entry for eric7 try: from importlib.metadata import version as meta_version - __EricToolVersion = str(meta_version('eric-ide')) + + __EricToolVersion = str(meta_version("eric-ide")) except Exception: from UI.Info import Version + __EricToolVersion = Version - - EricTool = Tool(vendor='python-projects.org', - name='eric-ide', - version=__EricToolVersion) - EricTool.external_references.update([ - ExternalReference( - reference_type=ExternalReferenceType.DISTRIBUTION, - url=XsUri( - "https://pypi.org/project/eric-ide/" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.DOCUMENTATION, - url=XsUri( - "https://pypi.org/project/eric-ide/" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.ISSUE_TRACKER, - url=XsUri( - "https://tracker.die-offenbachs.homelinux.org" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.LICENSE, - url=XsUri( - "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" - "LICENSE.GPL3" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.RELEASE_NOTES, - url=XsUri( - "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" - "changelog" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.VCS, - url=XsUri( - "https://hg.die-offenbachs.homelinux.org/eric" - ) - ), - ExternalReference( - reference_type=ExternalReferenceType.WEBSITE, - url=XsUri( - "https://eric-ide.python-projects.org" - ) - ) - ]) + + EricTool = Tool( + vendor="python-projects.org", name="eric-ide", version=__EricToolVersion + ) + EricTool.external_references.update( + [ + ExternalReference( + reference_type=ExternalReferenceType.DISTRIBUTION, + url=XsUri("https://pypi.org/project/eric-ide/"), + ), + ExternalReference( + reference_type=ExternalReferenceType.DOCUMENTATION, + url=XsUri("https://pypi.org/project/eric-ide/"), + ), + ExternalReference( + reference_type=ExternalReferenceType.ISSUE_TRACKER, + url=XsUri("https://tracker.die-offenbachs.homelinux.org"), + ), + ExternalReference( + reference_type=ExternalReferenceType.LICENSE, + url=XsUri( + "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" + "LICENSE.GPL3" + ), + ), + ExternalReference( + reference_type=ExternalReferenceType.RELEASE_NOTES, + url=XsUri( + "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" + "changelog" + ), + ), + ExternalReference( + reference_type=ExternalReferenceType.VCS, + url=XsUri("https://hg.die-offenbachs.homelinux.org/eric"), + ), + ExternalReference( + reference_type=ExternalReferenceType.WEBSITE, + url=XsUri("https://eric-ide.python-projects.org"), + ), + ] + ) bomMetaData.tools.add(EricTool) - + # add the meta data info entered by the user (if any) if metadataDict is not None: if metadataDict["AuthorName"]: - bomMetaData.authors = [OrganizationalContact( - name=metadataDict["AuthorName"], - email=metadataDict["AuthorEmail"] - )] + bomMetaData.authors = [ + OrganizationalContact( + name=metadataDict["AuthorName"], email=metadataDict["AuthorEmail"] + ) + ] if metadataDict["Manufacturer"]: bomMetaData.manufacture = OrganizationalEntity( name=metadataDict["Manufacturer"] ) if metadataDict["Supplier"]: - bomMetaData.supplier = OrganizationalEntity( - name=metadataDict["Supplier"]) + bomMetaData.supplier = OrganizationalEntity(name=metadataDict["Supplier"]) if metadataDict["License"]: - bomMetaData.licenses = [LicenseChoice( - license_expression=metadataDict["License"] - )] + bomMetaData.licenses = [ + LicenseChoice(license_expression=metadataDict["License"]) + ] if metadataDict["Name"]: bomMetaData.component = Component( name=metadataDict["Name"], @@ -356,9 +361,7 @@ version=metadataDict["Version"], description=metadataDict["Description"], author=metadataDict["AuthorName"], - licenses=[LicenseChoice( - license_expression=metadataDict["License"] - )], + licenses=[LicenseChoice(license_expression=metadataDict["License"])], ) - + return bomMetaData