CycloneDX eric7

Sat, 04 Jun 2022 15:53:41 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 04 Jun 2022 15:53:41 +0200
branch
eric7
changeset 9119
5bcdef5207f6
parent 9118
9858a6c957f5
child 9120
5fb0ffe30569

CycloneDX
- added capability to list vulnerabilities in the SBOM file

eric7/CycloneDXInterface/CycloneDXConfigDialog.py file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/CycloneDXConfigDialog.ui file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/CycloneDXUtilities.py file | annotate | diff | comparison | revisions
--- 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

eric ide

mercurial