Sat, 04 Jun 2022 15:53:41 +0200
CycloneDX
- added capability to list vulnerabilities in the SBOM file
--- a/eric7/CycloneDXInterface/CycloneDXConfigDialog.py Sat Jun 04 11:56:48 2022 +0200 +++ b/eric7/CycloneDXInterface/CycloneDXConfigDialog.py Sat Jun 04 15:53:41 2022 +0200 @@ -23,8 +23,18 @@ Class implementing a dialog to configure the CycloneDX SBOM generation. """ SupportedSchemas = { - "JSON": ["1.4", "1.3", "1.2"], - "XML": ["1.4", "1.3", "1.2", "1.1", "1.0"], + "JSON": [ + (1, 4), + (1, 3), + (1, 2), + ], + "XML": [ + (1, 4), + (1, 3), + (1, 2), + (1, 1), + (1, 0), + ], } Sources = { "pipenv": "Pipfile.lock", @@ -72,6 +82,9 @@ CycloneDXConfigDialog.Sources["requirements"] ))) + self.vulnerabilityCheckBox.toggled.connect( + self.__repopulateSchemaVersionComboBox) + self.filePicker.setMode(EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE) self.filePicker.setDefaultDirectory(self.__defaultDirectory) @@ -83,6 +96,24 @@ msh = self.minimumSizeHint() self.resize(max(self.width(), msh.width()), msh.height()) + @pyqtSlot() + def __repopulateSchemaVersionComboBox(self): + """ + Private slot to repopulate the schema version selector. + """ + fileFormat = self.fileFormatComboBox.currentText() + minSchemaVersion = ( + (1, 4) + if self.vulnerabilityCheckBox.isChecked() else + (1, 0) + ) + self.schemaVersionComboBox.clear() + self.schemaVersionComboBox.addItems( + "{0}.{1}".format(*f) + for f in CycloneDXConfigDialog.SupportedSchemas[fileFormat] + if f >= minSchemaVersion + ) + @pyqtSlot(str) def on_fileFormatComboBox_currentTextChanged(self, fileFormat): """ @@ -92,9 +123,7 @@ @type str """ # re-populate the file schema combo box - self.schemaVersionComboBox.clear() - self.schemaVersionComboBox.addItems( - CycloneDXConfigDialog.SupportedSchemas[fileFormat]) + self.__repopulateSchemaVersionComboBox() # set the file filter if fileFormat == "JSON": @@ -111,9 +140,9 @@ Public method to get the SBOM configuration data. @return tuple containing the input source, the input file name, the - file format, the schema version and the path of the SBOM file to - be written - @rtype tuple of (str, str, str, str, str) + file format, the schema version, the path of the SBOM file to be + written and a flag indicating to include vulnerability information + @rtype tuple of (str, str, str, str, str, bool) """ if self.environmentButton.isChecked(): inputSource = "environment" @@ -154,4 +183,7 @@ # should not happen sbomFile = None - return inputSource, inputFile, fileFormat, schemaVersion, sbomFile + return ( + inputSource, inputFile, fileFormat, schemaVersion, sbomFile, + self.vulnerabilityCheckBox.isChecked(), + )
--- a/eric7/CycloneDXInterface/CycloneDXConfigDialog.ui Sat Jun 04 11:56:48 2022 +0200 +++ b/eric7/CycloneDXInterface/CycloneDXConfigDialog.ui Sat Jun 04 15:53:41 2022 +0200 @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>650</width> - <height>278</height> + <height>308</height> </rect> </property> <property name="windowTitle"> @@ -99,14 +99,21 @@ <string>SBOM Output</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> + <item row="1" column="0"> <widget class="QLabel" name="label_2"> <property name="text"> <string>File Format:</string> </property> </widget> </item> - <item row="0" column="1"> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Schema Version:</string> + </property> + </widget> + </item> + <item row="1" column="1"> <widget class="QComboBox" name="fileFormatComboBox"> <property name="toolTip"> <string>Select the format of the SBOM file</string> @@ -123,7 +130,14 @@ </item> </widget> </item> - <item row="0" column="2"> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>File Name:</string> + </property> + </widget> + </item> + <item row="1" column="2"> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -136,34 +150,33 @@ </property> </spacer> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Schema Version:</string> + <item row="3" column="1" colspan="2"> + <widget class="EricPathPicker" name="filePicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the file path for the SBOM file (leave empty for default)</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="2" column="1"> <widget class="QComboBox" name="schemaVersionComboBox"> <property name="toolTip"> <string>Select the SBOM schema version of the SBOM file</string> </property> </widget> </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>File Name:</string> + <item row="0" column="0" colspan="3"> + <widget class="QCheckBox" name="vulnerabilityCheckBox"> + <property name="toolTip"> + <string>Select to include vulnerability data in the generated SBOM (requires Schema 1.4 or newer)</string> </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="EricPathPicker" name="filePicker" native="true"> - <property name="focusPolicy"> - <enum>Qt::StrongFocus</enum> + <property name="text"> + <string>Include Vulnerability Information</string> </property> - <property name="toolTip"> - <string>Enter the file path for the SBOM file (leave empty for default)</string> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> @@ -190,6 +203,16 @@ <container>1</container> </customwidget> </customwidgets> + <tabstops> + <tabstop>environmentButton</tabstop> + <tabstop>pipenvButton</tabstop> + <tabstop>poetryButton</tabstop> + <tabstop>requirementsButton</tabstop> + <tabstop>vulnerabilityCheckBox</tabstop> + <tabstop>fileFormatComboBox</tabstop> + <tabstop>schemaVersionComboBox</tabstop> + <tabstop>filePicker</tabstop> + </tabstops> <resources/> <connections> <connection>
--- a/eric7/CycloneDXInterface/CycloneDXUtilities.py Sat Jun 04 11:56:48 2022 +0200 +++ b/eric7/CycloneDXInterface/CycloneDXUtilities.py Sat Jun 04 15:53:41 2022 +0200 @@ -20,6 +20,7 @@ from cyclonedx.model import LicenseChoice 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 ) @@ -29,6 +30,10 @@ from cyclonedx_py.parser.poetry import PoetryFileParser from cyclonedx_py.parser.requirements import RequirementsFileParser +from PipInterface.PipVulnerabilityChecker import ( + Package, VulnerabilityCheckError +) + class CycloneDXEnvironmentParser(BaseParser): """ @@ -76,10 +81,10 @@ from .CycloneDXConfigDialog import CycloneDXConfigDialog dlg = CycloneDXConfigDialog(venvName) if dlg.exec() == QDialog.DialogCode.Accepted: - inputSource, inputFile, fileFormat, schemaVersion, sbomFile = ( - dlg.getData() - ) + (inputSource, inputFile, fileFormat, schemaVersion, sbomFile, + withVulnerabilities) = dlg.getData() + # check error conditions first if inputSource not in ("environment", "pipenv", "poetry", "requirements"): raise RuntimeError("Unsupported input source given.") @@ -110,6 +115,9 @@ elif inputSource == "requirements": parser = RequirementsFileParser(requirements_file=inputFile) + if withVulnerabilities: + addCycloneDXVulnerabilities(parser) + if fileFormat == "XML": outputFormat = OutputFormat.XML elif fileFormat == "JSON": @@ -150,3 +158,52 @@ "<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") + )) + + +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 + @type str + @return reference to the found component or None + @rtype Component or None + """ + for component in components: + if component.name == name: + return component + + return None