src/eric7/CycloneDXInterface/CycloneDXConfigDialog.py

Wed, 20 Jul 2022 17:52:08 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 20 Jul 2022 17:52:08 +0200
branch
eric7
changeset 9258
52a1ff7adb16
parent 9221
bf71ee032bb4
child 9278
36448ca469c2
permissions
-rw-r--r--

Implemented a little change to the CycloneDX configuration dialog to ensure the selected file has an extension and that it matches the selected format..

# -*- coding: utf-8 -*-

# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to configure the CycloneDX SBOM generation.
"""

import os

from PyQt6.QtCore import pyqtSlot
from PyQt6.QtWidgets import QDialog, QDialogButtonBox

from EricWidgets.EricApplication import ericApp
from EricWidgets.EricPathPicker import EricPathPickerModes

from .Ui_CycloneDXConfigDialog import Ui_CycloneDXConfigDialog


class CycloneDXConfigDialog(QDialog, Ui_CycloneDXConfigDialog):
    """
    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),
        ],
    }
    Sources = {
        "pipenv": "Pipfile.lock",
        "poetry": "poetry.lock",
        "requirements": "requirements.txt",
    }
    DefaultFileFormat = "JSON"
    DefaultFileNames = {
        "JSON": "cyclonedx.json",
        "XML": "cyclonedx.xml",
    }

    def __init__(self, environment, parent=None):
        """
        Constructor

        @param environment name of the virtual environment
        @type str
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setupUi(self)

        if environment == "<project>":
            self.__project = ericApp().getObject("Project")
            self.__defaultDirectory = self.__project.getProjectPath()
        else:
            self.__project = None
            venvManager = ericApp().getObject("VirtualEnvManager")
            self.__defaultDirectory = venvManager.getVirtualenvDirectory(environment)

        self.environmentLabel.setText(environment)

        self.pipenvButton.setEnabled(
            os.path.isfile(
                os.path.join(
                    self.__defaultDirectory, CycloneDXConfigDialog.Sources["pipenv"]
                )
            )
        )
        self.poetryButton.setEnabled(
            os.path.isfile(
                os.path.join(
                    self.__defaultDirectory, CycloneDXConfigDialog.Sources["poetry"]
                )
            )
        )
        self.requirementsButton.setEnabled(
            os.path.isfile(
                os.path.join(
                    self.__defaultDirectory,
                    CycloneDXConfigDialog.Sources["requirements"],
                )
            )
        )

        self.vulnerabilityCheckBox.toggled.connect(
            self.__repopulateSchemaVersionComboBox
        )

        self.filePicker.setMode(EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE)
        self.filePicker.setDefaultDirectory(self.__defaultDirectory)

        self.fileFormatComboBox.setCurrentText(CycloneDXConfigDialog.DefaultFileFormat)
        self.on_fileFormatComboBox_currentTextChanged(
            CycloneDXConfigDialog.DefaultFileFormat
        )

        self.__metadata = None
        self.__metadataButton = self.buttonBox.addButton(
            self.tr("Edit Metadata..."), QDialogButtonBox.ButtonRole.ActionRole
        )
        self.__metadataButton.clicked.connect(self.__editMetaData)

        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):
        """
        Private slot to handle the selection of a SBOM file format.

        @param fileFormat selected format
        @type str
        """
        # re-populate the file schema combo box
        self.__repopulateSchemaVersionComboBox()

        # set the file filter
        if fileFormat == "JSON":
            self.filePicker.setFilters(self.tr("JSON Files (*.json);;All Files (*)"))
            suffix = ".json"
        elif fileFormat == "XML":
            self.filePicker.setFilters(self.tr("XML Files (*.xml);;All Files (*)"))
            suffix = ".xml"
        else:
            self.filePicker.setFilters(self.tr("All Files (*)"))
            suffix = ""
        
        filePath = self.filePicker.path()
        if bool(filePath.name):
            self.filePicker.setPath(filePath.with_suffix(suffix))

    @pyqtSlot()
    def __editMetaData(self):
        """
        Private slot to open a dialog for editing the SBOM metadata.
        """
        from .CycloneDXMetaDataDialog import CycloneDXMetaDataDialog

        # populate a metadata dictionary from project data
        metadata = (
            {
                "Name": self.__project.getProjectName(),
                "Type": "",
                "Version": self.__project.getProjectVersion(),
                "Description": self.__project.getProjectDescription(),
                "AuthorName": self.__project.getProjectAuthor(),
                "AuthorEmail": self.__project.getProjectAuthorEmail(),
                "License": self.__project.getProjectLicense(),
                "Manufacturer": "",
                "Supplier": "",
            }
            if self.__metadata is None and self.__project is not None
            else self.__metadata
        )

        dlg = CycloneDXMetaDataDialog(metadata=metadata, parent=self)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            self.__metadata = dlg.getMetaData()

    def getData(self):
        """
        Public method to get the SBOM configuration data.

        @return tuple containing the input source, the input file name, the
            file format, the schema version, the path of the SBOM file to be
            written, a flag indicating to include vulnerability information,
            a flag indicating to include dependency information and a
            dictionary containing the SBOM meta data
        @rtype tuple of (str, str, str, str, str, bool, bool, dict)
        """
        if self.environmentButton.isChecked():
            inputSource = "environment"
            inputFile = None
        elif self.pipenvButton.isChecked():
            inputSource = "pipenv"
            inputFile = os.path.join(
                self.__defaultDirectory, CycloneDXConfigDialog.Sources["pipenv"]
            )
        elif self.poetryButton.isChecked():
            inputSource = "poetry"
            inputFile = os.path.join(
                self.__defaultDirectory, CycloneDXConfigDialog.Sources["poetry"]
            )
        elif self.requirementsButton.isChecked():
            inputSource = "requirements"
            inputFile = os.path.join(
                self.__defaultDirectory, CycloneDXConfigDialog.Sources["requirements"]
            )
        else:
            # should not happen
            inputSource = None
            inputFile = None

        fileFormat = self.fileFormatComboBox.currentText()
        schemaVersion = self.schemaVersionComboBox.currentText()
        sbomFile = self.filePicker.text()
        if not sbomFile:
            try:
                sbomFile = os.path.join(
                    self.__defaultDirectory,
                    CycloneDXConfigDialog.DefaultFileNames[fileFormat],
                )
            except KeyError:
                # should not happen
                sbomFile = None

        return (
            inputSource,
            inputFile,
            fileFormat,
            schemaVersion,
            sbomFile,
            self.vulnerabilityCheckBox.isChecked(),
            self.dependenciesCheckBox.isChecked(),
            self.__metadata,
        )

eric ide

mercurial