src/eric7/CycloneDXInterface/CycloneDXUtilities.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 from EricWidgets import EricMessageBox 16 from EricWidgets import EricMessageBox
17 17
18 from packageurl import PackageURL 18 from packageurl import PackageURL
19 19
20 from cyclonedx.model import ( 20 from cyclonedx.model import (
21 ExternalReference, ExternalReferenceType, LicenseChoice, 21 ExternalReference,
22 OrganizationalContact, OrganizationalEntity, Tool, XsUri 22 ExternalReferenceType,
23 LicenseChoice,
24 OrganizationalContact,
25 OrganizationalEntity,
26 Tool,
27 XsUri,
23 ) 28 )
24 from cyclonedx.model.bom import Bom 29 from cyclonedx.model.bom import Bom
25 from cyclonedx.model.component import Component 30 from cyclonedx.model.component import Component
26 from cyclonedx.model.vulnerability import Vulnerability, VulnerabilitySource 31 from cyclonedx.model.vulnerability import Vulnerability, VulnerabilitySource
27 from cyclonedx.output import ( 32 from cyclonedx.output import (
28 OutputFormat, SchemaVersion, get_instance as get_output_instance 33 OutputFormat,
34 SchemaVersion,
35 get_instance as get_output_instance,
29 ) 36 )
30 from cyclonedx.parser import BaseParser 37 from cyclonedx.parser import BaseParser
31 38
32 from cyclonedx_py.parser.pipenv import PipEnvFileParser 39 from cyclonedx_py.parser.pipenv import PipEnvFileParser
33 from cyclonedx_py.parser.poetry import PoetryFileParser 40 from cyclonedx_py.parser.poetry import PoetryFileParser
34 from cyclonedx_py.parser.requirements import RequirementsFileParser 41 from cyclonedx_py.parser.requirements import RequirementsFileParser
35 42
36 from PipInterface.PipVulnerabilityChecker import ( 43 from PipInterface.PipVulnerabilityChecker import Package, VulnerabilityCheckError
37 Package, VulnerabilityCheckError
38 )
39 44
40 45
41 class CycloneDXEnvironmentParser(BaseParser): 46 class CycloneDXEnvironmentParser(BaseParser):
42 """ 47 """
43 Class implementing a parser to get package data for a named environment. 48 Class implementing a parser to get package data for a named environment.
44 """ 49 """
50
45 def __init__(self, venvName): 51 def __init__(self, venvName):
46 """ 52 """
47 Constructor 53 Constructor
48 54
49 @param venvName name of the virtual environment 55 @param venvName name of the virtual environment
50 @type str 56 @type str
51 """ 57 """
52 super().__init__() 58 super().__init__()
53 59
54 pip = ericApp().getObject("Pip") 60 pip = ericApp().getObject("Pip")
55 packages = pip.getLicenses(venvName) 61 packages = pip.getLicenses(venvName)
56 for package in packages: 62 for package in packages:
57 comp = Component( 63 comp = Component(
58 name=package["Name"], 64 name=package["Name"],
59 version=package["Version"], 65 version=package["Version"],
60 author=package["Author"], 66 author=package["Author"],
61 description=package["Description"], 67 description=package["Description"],
62 purl=PackageURL( 68 purl=PackageURL(
63 type='pypi', 69 type="pypi", name=package["Name"], version=package["Version"]
64 name=package["Name"], 70 ),
65 version=package["Version"]
66 )
67 ) 71 )
68 for lic in package["License"].split(";"): 72 for lic in package["License"].split(";"):
69 comp.licenses.add( 73 comp.licenses.add(LicenseChoice(license_expression=lic.strip()))
70 LicenseChoice(license_expression=lic.strip()) 74
71 )
72
73 self._components.append(comp) 75 self._components.append(comp)
74 76
75 77
76 def createCycloneDXFile(venvName): 78 def createCycloneDXFile(venvName):
77 """ 79 """
78 Function to create a CyccloneDX SBOM file. 80 Function to create a CyccloneDX SBOM file.
79 81
80 @param venvName name of the virtual environment 82 @param venvName name of the virtual environment
81 @type str 83 @type str
82 @exception RuntimeError raised to indicate illegal creation parameters 84 @exception RuntimeError raised to indicate illegal creation parameters
83 """ 85 """
84 from .CycloneDXConfigDialog import CycloneDXConfigDialog 86 from .CycloneDXConfigDialog import CycloneDXConfigDialog
87
85 dlg = CycloneDXConfigDialog(venvName) 88 dlg = CycloneDXConfigDialog(venvName)
86 if dlg.exec() == QDialog.DialogCode.Accepted: 89 if dlg.exec() == QDialog.DialogCode.Accepted:
87 (inputSource, inputFile, fileFormat, schemaVersion, sbomFile, 90 (
88 withVulnerabilities, withDependencies, metadataDict) = dlg.getData() 91 inputSource,
89 92 inputFile,
93 fileFormat,
94 schemaVersion,
95 sbomFile,
96 withVulnerabilities,
97 withDependencies,
98 metadataDict,
99 ) = dlg.getData()
100
90 # check error conditions first 101 # check error conditions first
91 if inputSource not in ("environment", "pipenv", "poetry", 102 if inputSource not in ("environment", "pipenv", "poetry", "requirements"):
92 "requirements"):
93 raise RuntimeError("Unsupported input source given.") 103 raise RuntimeError("Unsupported input source given.")
94 if fileFormat not in ("XML", "JSON"): 104 if fileFormat not in ("XML", "JSON"):
95 raise RuntimeError("Unsupported SBOM file format given.") 105 raise RuntimeError("Unsupported SBOM file format given.")
96 106
97 if inputSource == "environment": 107 if inputSource == "environment":
98 parser = CycloneDXEnvironmentParser(venvName) 108 parser = CycloneDXEnvironmentParser(venvName)
99 else: 109 else:
100 # all other parsers need an input file 110 # all other parsers need an input file
101 if not os.path.isfile(inputFile): 111 if not os.path.isfile(inputFile):
102 EricMessageBox.warning( 112 EricMessageBox.warning(
103 None, 113 None,
104 QCoreApplication.translate( 114 QCoreApplication.translate(
105 "CycloneDX", "CycloneDX - SBOM Creation"), 115 "CycloneDX", "CycloneDX - SBOM Creation"
116 ),
106 QCoreApplication.translate( 117 QCoreApplication.translate(
107 "CycloneDX", 118 "CycloneDX",
108 "<p>The configured input file <b>{0}</b> does not" 119 "<p>The configured input file <b>{0}</b> does not"
109 " exist. Aborting...</p>" 120 " exist. Aborting...</p>",
110 ).format(inputFile) 121 ).format(inputFile),
111 ) 122 )
112 return 123 return
113 124
114 if inputSource == "pipenv": 125 if inputSource == "pipenv":
115 parser = PipEnvFileParser(pipenv_lock_filename=inputFile) 126 parser = PipEnvFileParser(pipenv_lock_filename=inputFile)
116 elif inputSource == "poetry": 127 elif inputSource == "poetry":
117 parser = PoetryFileParser(poetry_lock_filename=inputFile) 128 parser = PoetryFileParser(poetry_lock_filename=inputFile)
118 elif inputSource == "requirements": 129 elif inputSource == "requirements":
119 parser = RequirementsFileParser(requirements_file=inputFile) 130 parser = RequirementsFileParser(requirements_file=inputFile)
120 131
121 if withVulnerabilities: 132 if withVulnerabilities:
122 addCycloneDXVulnerabilities(parser) 133 addCycloneDXVulnerabilities(parser)
123 134
124 if withDependencies: 135 if withDependencies:
125 addCycloneDXDependencies(parser, venvName) 136 addCycloneDXDependencies(parser, venvName)
126 137
127 if fileFormat == "XML": 138 if fileFormat == "XML":
128 outputFormat = OutputFormat.XML 139 outputFormat = OutputFormat.XML
129 elif fileFormat == "JSON": 140 elif fileFormat == "JSON":
130 outputFormat = OutputFormat.JSON 141 outputFormat = OutputFormat.JSON
131 142
132 if parser.has_warnings(): 143 if parser.has_warnings():
133 excludedList = ["<li>{0}</li>".format(warning.get_item()) 144 excludedList = [
134 for warning in parser.get_warnings()] 145 "<li>{0}</li>".format(warning.get_item())
146 for warning in parser.get_warnings()
147 ]
135 EricMessageBox.warning( 148 EricMessageBox.warning(
136 None, 149 None,
137 QCoreApplication.translate( 150 QCoreApplication.translate("CycloneDX", "CycloneDX - SBOM Creation"),
138 "CycloneDX", "CycloneDX - SBOM Creation"),
139 QCoreApplication.translate( 151 QCoreApplication.translate(
140 "CycloneDX", 152 "CycloneDX",
141 "<p>Some of the dependencies do not have pinned version" 153 "<p>Some of the dependencies do not have pinned version"
142 " numbers.<ul>{0}</ul>The above listed packages will NOT" 154 " numbers.<ul>{0}</ul>The above listed packages will NOT"
143 " be included in the generated CycloneDX SBOM file as" 155 " be included in the generated CycloneDX SBOM file as"
144 " version is a mandatory field.</p>" 156 " version is a mandatory field.</p>",
145 ).format("".join(excludedList)) 157 ).format("".join(excludedList)),
146 ) 158 )
147 159
148 bom = Bom.from_parser(parser=parser) 160 bom = Bom.from_parser(parser=parser)
149 _amendMetaData(bom.metadata, metadataDict) 161 _amendMetaData(bom.metadata, metadataDict)
150 output = get_output_instance( 162 output = get_output_instance(
151 bom=bom, 163 bom=bom,
152 output_format=outputFormat, 164 output_format=outputFormat,
153 schema_version=SchemaVersion['V{0}'.format( 165 schema_version=SchemaVersion[
154 schemaVersion.replace('.', '_') 166 "V{0}".format(schemaVersion.replace(".", "_"))
155 )] 167 ],
156 ) 168 )
157 output.output_to_file(filename=sbomFile, allow_overwrite=True) 169 output.output_to_file(filename=sbomFile, allow_overwrite=True)
158 170
159 EricMessageBox.information( 171 EricMessageBox.information(
160 None, 172 None,
173 QCoreApplication.translate("CycloneDX", "CycloneDX - SBOM Creation"),
161 QCoreApplication.translate( 174 QCoreApplication.translate(
162 "CycloneDX", "CycloneDX - SBOM Creation"), 175 "CycloneDX", "<p>The SBOM data was written to file <b>{0}</b>.</p>"
163 QCoreApplication.translate( 176 ).format(sbomFile),
164 "CycloneDX",
165 "<p>The SBOM data was written to file <b>{0}</b>.</p>"
166 ).format(sbomFile)
167 ) 177 )
168 178
169 179
170 def addCycloneDXVulnerabilities(parser): 180 def addCycloneDXVulnerabilities(parser):
171 """ 181 """
172 Function to add vulnerability data to the list of created components. 182 Function to add vulnerability data to the list of created components.
173 183
174 @param parser reference to the parser object containing the list of 184 @param parser reference to the parser object containing the list of
175 components 185 components
176 @type BaseParser 186 @type BaseParser
177 """ 187 """
178 components = parser.get_components() 188 components = parser.get_components()
179 189
180 packages = [ 190 packages = [
181 Package(name=component.name, version=component.version) 191 Package(name=component.name, version=component.version)
182 for component in components 192 for component in components
183 ] 193 ]
184 194
185 pip = ericApp().getObject("Pip") 195 pip = ericApp().getObject("Pip")
186 error, vulnerabilities = pip.getVulnerabilityChecker().check(packages) 196 error, vulnerabilities = pip.getVulnerabilityChecker().check(packages)
187 197
188 if error == VulnerabilityCheckError.OK: 198 if error == VulnerabilityCheckError.OK:
189 for package in vulnerabilities: 199 for package in vulnerabilities:
190 component = findCyccloneDXComponent(components, package) 200 component = findCyccloneDXComponent(components, package)
191 if component: 201 if component:
192 for vuln in vulnerabilities[package]: 202 for vuln in vulnerabilities[package]:
193 component.add_vulnerability(Vulnerability( 203 component.add_vulnerability(
194 id=vuln.cve, 204 Vulnerability(
195 description=vuln.advisory, 205 id=vuln.cve,
196 recommendation="upgrade required", 206 description=vuln.advisory,
197 source=VulnerabilitySource(name="pyup.io") 207 recommendation="upgrade required",
198 )) 208 source=VulnerabilitySource(name="pyup.io"),
209 )
210 )
199 211
200 212
201 def addCycloneDXDependencies(parser, venvName): 213 def addCycloneDXDependencies(parser, venvName):
202 """ 214 """
203 Function to add dependency data to the list of created components. 215 Function to add dependency data to the list of created components.
204 216
205 @param parser reference to the parser object containing the list of 217 @param parser reference to the parser object containing the list of
206 components 218 components
207 @type BaseParser 219 @type BaseParser
208 @param venvName name of the virtual environment 220 @param venvName name of the virtual environment
209 @type str 221 @type str
210 """ 222 """
211 components = parser.get_components() 223 components = parser.get_components()
212 224
213 pip = ericApp().getObject("Pip") 225 pip = ericApp().getObject("Pip")
214 dependencies = pip.getDependencyTree(venvName) 226 dependencies = pip.getDependencyTree(venvName)
215 for dependency in dependencies: 227 for dependency in dependencies:
216 _addCycloneDXDependency(dependency, components) 228 _addCycloneDXDependency(dependency, components)
217 229
218 230
219 def _addCycloneDXDependency(dependency, components): 231 def _addCycloneDXDependency(dependency, components):
220 """ 232 """
221 Function to add a dependency to the given list of components. 233 Function to add a dependency to the given list of components.
222 234
223 @param dependency dependency to be added 235 @param dependency dependency to be added
224 @type dict 236 @type dict
225 @param components list of components 237 @param components list of components
226 @type list of Component 238 @type list of Component
227 """ 239 """
228 component = findCyccloneDXComponent(components, dependency["package_name"]) 240 component = findCyccloneDXComponent(components, dependency["package_name"])
229 if component is not None: 241 if component is not None:
230 bomRefs = component.dependencies 242 bomRefs = component.dependencies
231 for dep in dependency["dependencies"]: 243 for dep in dependency["dependencies"]:
232 depComponent = findCyccloneDXComponent( 244 depComponent = findCyccloneDXComponent(components, dep["package_name"])
233 components, dep["package_name"])
234 if depComponent is not None: 245 if depComponent is not None:
235 bomRefs.add(depComponent.bom_ref) 246 bomRefs.add(depComponent.bom_ref)
236 # recursively add sub-dependencies 247 # recursively add sub-dependencies
237 _addCycloneDXDependency(dep, components) 248 _addCycloneDXDependency(dep, components)
238 component.dependencies = bomRefs 249 component.dependencies = bomRefs
239 250
240 251
241 def findCyccloneDXComponent(components, name): 252 def findCyccloneDXComponent(components, name):
242 """ 253 """
243 Function to find a component in a given list of components. 254 Function to find a component in a given list of components.
244 255
245 @param components list of components to scan 256 @param components list of components to scan
246 @type list of Component 257 @type list of Component
247 @param name name of the component to search for 258 @param name name of the component to search for
248 @type str 259 @type str
249 @return reference to the found component or None 260 @return reference to the found component or None
250 @rtype Component or None 261 @rtype Component or None
251 """ 262 """
252 for component in components: 263 for component in components:
253 if component.name == name: 264 if component.name == name:
254 return component 265 return component
255 266
256 return None 267 return None
257 268
258 269
259 def _amendMetaData(bomMetaData, metadataDict): 270 def _amendMetaData(bomMetaData, metadataDict):
260 """ 271 """
261 Function to amend the SBOM meta data according the given data. 272 Function to amend the SBOM meta data according the given data.
262 273
263 The modifications done are: 274 The modifications done are:
264 <ul> 275 <ul>
265 <li>add eric7 to the tools</li> 276 <li>add eric7 to the tools</li>
266 </ul> 277 </ul>
267 278
268 @param bomMetaData reference to the SBOM meta data object 279 @param bomMetaData reference to the SBOM meta data object
269 @type BomMetaData 280 @type BomMetaData
270 @param metadataDict dictionary containing additional meta data 281 @param metadataDict dictionary containing additional meta data
271 @type dict 282 @type dict
272 @return reference to the modified SBOM meta data object 283 @return reference to the modified SBOM meta data object
273 @rtype BomMetaData 284 @rtype BomMetaData
274 """ 285 """
275 # add a Tool entry for eric7 286 # add a Tool entry for eric7
276 try: 287 try:
277 from importlib.metadata import version as meta_version 288 from importlib.metadata import version as meta_version
278 __EricToolVersion = str(meta_version('eric-ide')) 289
290 __EricToolVersion = str(meta_version("eric-ide"))
279 except Exception: 291 except Exception:
280 from UI.Info import Version 292 from UI.Info import Version
293
281 __EricToolVersion = Version 294 __EricToolVersion = Version
282 295
283 EricTool = Tool(vendor='python-projects.org', 296 EricTool = Tool(
284 name='eric-ide', 297 vendor="python-projects.org", name="eric-ide", version=__EricToolVersion
285 version=__EricToolVersion) 298 )
286 EricTool.external_references.update([ 299 EricTool.external_references.update(
287 ExternalReference( 300 [
288 reference_type=ExternalReferenceType.DISTRIBUTION, 301 ExternalReference(
289 url=XsUri( 302 reference_type=ExternalReferenceType.DISTRIBUTION,
290 "https://pypi.org/project/eric-ide/" 303 url=XsUri("https://pypi.org/project/eric-ide/"),
291 ) 304 ),
292 ), 305 ExternalReference(
293 ExternalReference( 306 reference_type=ExternalReferenceType.DOCUMENTATION,
294 reference_type=ExternalReferenceType.DOCUMENTATION, 307 url=XsUri("https://pypi.org/project/eric-ide/"),
295 url=XsUri( 308 ),
296 "https://pypi.org/project/eric-ide/" 309 ExternalReference(
297 ) 310 reference_type=ExternalReferenceType.ISSUE_TRACKER,
298 ), 311 url=XsUri("https://tracker.die-offenbachs.homelinux.org"),
299 ExternalReference( 312 ),
300 reference_type=ExternalReferenceType.ISSUE_TRACKER, 313 ExternalReference(
301 url=XsUri( 314 reference_type=ExternalReferenceType.LICENSE,
302 "https://tracker.die-offenbachs.homelinux.org" 315 url=XsUri(
303 ) 316 "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/"
304 ), 317 "LICENSE.GPL3"
305 ExternalReference( 318 ),
306 reference_type=ExternalReferenceType.LICENSE, 319 ),
307 url=XsUri( 320 ExternalReference(
308 "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" 321 reference_type=ExternalReferenceType.RELEASE_NOTES,
309 "LICENSE.GPL3" 322 url=XsUri(
310 ) 323 "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/"
311 ), 324 "changelog"
312 ExternalReference( 325 ),
313 reference_type=ExternalReferenceType.RELEASE_NOTES, 326 ),
314 url=XsUri( 327 ExternalReference(
315 "https://hg.die-offenbachs.homelinux.org/eric/file/tip/docs/" 328 reference_type=ExternalReferenceType.VCS,
316 "changelog" 329 url=XsUri("https://hg.die-offenbachs.homelinux.org/eric"),
317 ) 330 ),
318 ), 331 ExternalReference(
319 ExternalReference( 332 reference_type=ExternalReferenceType.WEBSITE,
320 reference_type=ExternalReferenceType.VCS, 333 url=XsUri("https://eric-ide.python-projects.org"),
321 url=XsUri( 334 ),
322 "https://hg.die-offenbachs.homelinux.org/eric" 335 ]
323 ) 336 )
324 ),
325 ExternalReference(
326 reference_type=ExternalReferenceType.WEBSITE,
327 url=XsUri(
328 "https://eric-ide.python-projects.org"
329 )
330 )
331 ])
332 bomMetaData.tools.add(EricTool) 337 bomMetaData.tools.add(EricTool)
333 338
334 # add the meta data info entered by the user (if any) 339 # add the meta data info entered by the user (if any)
335 if metadataDict is not None: 340 if metadataDict is not None:
336 if metadataDict["AuthorName"]: 341 if metadataDict["AuthorName"]:
337 bomMetaData.authors = [OrganizationalContact( 342 bomMetaData.authors = [
338 name=metadataDict["AuthorName"], 343 OrganizationalContact(
339 email=metadataDict["AuthorEmail"] 344 name=metadataDict["AuthorName"], email=metadataDict["AuthorEmail"]
340 )] 345 )
346 ]
341 if metadataDict["Manufacturer"]: 347 if metadataDict["Manufacturer"]:
342 bomMetaData.manufacture = OrganizationalEntity( 348 bomMetaData.manufacture = OrganizationalEntity(
343 name=metadataDict["Manufacturer"] 349 name=metadataDict["Manufacturer"]
344 ) 350 )
345 if metadataDict["Supplier"]: 351 if metadataDict["Supplier"]:
346 bomMetaData.supplier = OrganizationalEntity( 352 bomMetaData.supplier = OrganizationalEntity(name=metadataDict["Supplier"])
347 name=metadataDict["Supplier"])
348 if metadataDict["License"]: 353 if metadataDict["License"]:
349 bomMetaData.licenses = [LicenseChoice( 354 bomMetaData.licenses = [
350 license_expression=metadataDict["License"] 355 LicenseChoice(license_expression=metadataDict["License"])
351 )] 356 ]
352 if metadataDict["Name"]: 357 if metadataDict["Name"]:
353 bomMetaData.component = Component( 358 bomMetaData.component = Component(
354 name=metadataDict["Name"], 359 name=metadataDict["Name"],
355 component_type=metadataDict["Type"], 360 component_type=metadataDict["Type"],
356 version=metadataDict["Version"], 361 version=metadataDict["Version"],
357 description=metadataDict["Description"], 362 description=metadataDict["Description"],
358 author=metadataDict["AuthorName"], 363 author=metadataDict["AuthorName"],
359 licenses=[LicenseChoice( 364 licenses=[LicenseChoice(license_expression=metadataDict["License"])],
360 license_expression=metadataDict["License"]
361 )],
362 ) 365 )
363 366
364 return bomMetaData 367 return bomMetaData

eric ide

mercurial