eric7/CycloneDXInterface/CycloneDXUtilities.py

Fri, 03 Jun 2022 19:54:57 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 03 Jun 2022 19:54:57 +0200
branch
eric7
changeset 9117
c6afba2049cf
child 9119
5bcdef5207f6
permissions
-rw-r--r--

CycloneDX Interface
- added capability to create a Software Bill of Materials (SBOM) file in CycloneDX format
- added this capability to the pip Interface and Project

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

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

"""
Module implementing the interface to CycloneDX.
"""

import os

from PyQt6.QtCore import QCoreApplication
from PyQt6.QtWidgets import QDialog

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox

from packageurl import PackageURL

from cyclonedx.model import LicenseChoice
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component
from cyclonedx.output import (
    OutputFormat, SchemaVersion, get_instance as get_output_instance
)
from cyclonedx.parser import BaseParser

from cyclonedx_py.parser.pipenv import PipEnvFileParser
from cyclonedx_py.parser.poetry import PoetryFileParser
from cyclonedx_py.parser.requirements import RequirementsFileParser


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:
            comp = Component(
                name=package["Name"],
                version=package["Version"],
                author=package["Author"],
                description=package["Description"],
                purl=PackageURL(
                    type='pypi',
                    name=package["Name"],
                    version=package["Version"]
                )
            )
            for lic in package["License"].split(";"):
                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 = (
            dlg.getData()
        )
        
        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:
            # all other parsers need an input file
            if not os.path.isfile(inputFile):
                EricMessageBox.warning(
                    None,
                    QCoreApplication.translate(
                        "CycloneDX", "CycloneDX - SBOM Creation"),
                    QCoreApplication.translate(
                        "CycloneDX",
                        "<p>The configured input file <b>{0}</b> does not"
                        " 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 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()]
            EricMessageBox.warning(
                None,
                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))
            )
        
        bom = Bom.from_parser(parser=parser)
        output = get_output_instance(
            bom=bom,
            output_format=outputFormat,
            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",
                "<p>The SBOM data was written to file <b>{0}</b>.</p>"
            ).format(sbomFile)
        )

eric ide

mercurial