66import logging
77import os
88import tempfile
9+ from dataclasses import dataclass
910from functools import partial
1011
1112from packageurl import PackageURL
3031logger : logging .Logger = logging .getLogger (__name__ )
3132
3233
34+ @dataclass (frozen = True )
35+ class ProvenanceAsset :
36+ """This class exists to hold a provenance payload with the original asset's name and URL."""
37+
38+ payload : InTotoPayload
39+ name : str
40+ url : str
41+
42+
3343class ProvenanceFinder :
3444 """This class is used to find and retrieve provenance files from supported registries."""
3545
@@ -44,7 +54,7 @@ def __init__(self) -> None:
4454 elif isinstance (registry , JFrogMavenRegistry ):
4555 self .jfrog_registry = registry
4656
47- def find_provenance (self , purl : PackageURL ) -> list [InTotoPayload ]:
57+ def find_provenance (self , purl : PackageURL ) -> list [ProvenanceAsset ]:
4858 """Find the provenance file(s) of the passed PURL.
4959
5060 Parameters
@@ -54,8 +64,8 @@ def find_provenance(self, purl: PackageURL) -> list[InTotoPayload]:
5464
5565 Returns
5666 -------
57- list[InTotoPayload ]
58- The provenance payload , or an empty list if not found.
67+ list[ProvenanceAsset ]
68+ The provenance asset , or an empty list if not found.
5969 """
6070 logger .debug ("Seeking provenance of: %s" , purl )
6171
@@ -88,7 +98,7 @@ def find_provenance(self, purl: PackageURL) -> list[InTotoPayload]:
8898 logger .debug ("Provenance finding not supported for PURL type: %s" , purl .type )
8999 return []
90100
91- def _find_provenance (self , discovery_functions : list [partial [list [InTotoPayload ]]]) -> list [InTotoPayload ]:
101+ def _find_provenance (self , discovery_functions : list [partial [list [ProvenanceAsset ]]]) -> list [ProvenanceAsset ]:
92102 """Find the provenance file(s) using the passed discovery functions.
93103
94104 Parameters
@@ -99,7 +109,7 @@ def _find_provenance(self, discovery_functions: list[partial[list[InTotoPayload]
99109 Returns
100110 -------
101111 list[InTotoPayload]
102- The provenance payload (s) from the first successful function, or an empty list if none were.
112+ The provenance asset (s) from the first successful function, or an empty list if none were.
103113 """
104114 if not discovery_functions :
105115 return []
@@ -114,7 +124,7 @@ def _find_provenance(self, discovery_functions: list[partial[list[InTotoPayload]
114124 return []
115125
116126
117- def find_npm_provenance (purl : PackageURL , registry : NPMRegistry ) -> list [InTotoPayload ]:
127+ def find_npm_provenance (purl : PackageURL , registry : NPMRegistry ) -> list [ProvenanceAsset ]:
118128 """Find and download the NPM based provenance for the passed PURL.
119129
120130 Two kinds of attestation can be retrieved from npm: "Provenance" and "Publish". The "Provenance" attestation
@@ -131,8 +141,8 @@ def find_npm_provenance(purl: PackageURL, registry: NPMRegistry) -> list[InTotoP
131141
132142 Returns
133143 -------
134- list[InTotoPayload ]
135- The provenance payload (s), or an empty list if not found.
144+ list[ProvenanceAsset ]
145+ The provenance asset (s), or an empty list if not found.
136146 """
137147 if not registry .enabled :
138148 logger .debug ("The npm registry is not enabled." )
@@ -178,16 +188,19 @@ def find_npm_provenance(purl: PackageURL, registry: NPMRegistry) -> list[InTotoP
178188 publish_payload = load_provenance_payload (signed_download_path )
179189 except LoadIntotoAttestationError as error :
180190 logger .error ("Error while loading publish attestation: %s" , error )
181- return [provenance_payload ]
191+ return [ProvenanceAsset ( provenance_payload , npm_provenance_asset . name , npm_provenance_asset . url ) ]
182192
183- return [provenance_payload , publish_payload ]
193+ return [
194+ ProvenanceAsset (provenance_payload , npm_provenance_asset .name , npm_provenance_asset .url ),
195+ ProvenanceAsset (publish_payload , npm_provenance_asset .name , npm_provenance_asset .url ),
196+ ]
184197
185198 except OSError as error :
186199 logger .error ("Error while storing provenance in the temporary directory: %s" , error )
187200 return []
188201
189202
190- def find_gav_provenance (purl : PackageURL , registry : JFrogMavenRegistry ) -> list [InTotoPayload ]:
203+ def find_gav_provenance (purl : PackageURL , registry : JFrogMavenRegistry ) -> list [ProvenanceAsset ]:
191204 """Find and download the GAV based provenance for the passed PURL.
192205
193206 Parameters
@@ -199,8 +212,8 @@ def find_gav_provenance(purl: PackageURL, registry: JFrogMavenRegistry) -> list[
199212
200213 Returns
201214 -------
202- list[InTotoPayload ] | None
203- The provenance payload if found, or an empty list otherwise.
215+ list[ProvenanceAsset ] | None
216+ The provenance asset if found, or an empty list otherwise.
204217
205218 Raises
206219 ------
@@ -269,7 +282,7 @@ def find_gav_provenance(purl: PackageURL, registry: JFrogMavenRegistry) -> list[
269282 if not is_witness_provenance_payload (provenance_payload , witness_verifier_config .predicate_types ):
270283 continue
271284
272- provenances .append (provenance_payload )
285+ provenances .append (ProvenanceAsset ( provenance_payload , provenance_asset . name , provenance_asset . url ) )
273286 except OSError as error :
274287 logger .error ("Error while storing provenance in the temporary directory: %s" , error )
275288
@@ -281,7 +294,7 @@ def find_gav_provenance(purl: PackageURL, registry: JFrogMavenRegistry) -> list[
281294 return provenances [:1 ]
282295
283296
284- def find_pypi_provenance (purl : PackageURL ) -> list [InTotoPayload ]:
297+ def find_pypi_provenance (purl : PackageURL ) -> list [ProvenanceAsset ]:
285298 """Find and download the PyPI based provenance for the passed PURL.
286299
287300 Parameters
@@ -291,11 +304,11 @@ def find_pypi_provenance(purl: PackageURL) -> list[InTotoPayload]:
291304
292305 Returns
293306 -------
294- list[InTotoPayload] | None
295- The provenance payload if found, or an empty list otherwise.
307+ list[ProvenanceAsset]
308+ The provenance assets found, or an empty list otherwise.
296309 """
297- attestation , verified = DepsDevRepoFinder .get_attestation (purl )
298- if not attestation :
310+ attestation , url , verified = DepsDevRepoFinder .get_attestation (purl )
311+ if not ( attestation and url ) :
299312 return []
300313
301314 with tempfile .TemporaryDirectory () as temp_dir :
@@ -306,15 +319,15 @@ def find_pypi_provenance(purl: PackageURL) -> list[InTotoPayload]:
306319 try :
307320 payload = load_provenance_payload (file_name )
308321 payload .verified = verified
309- return [payload ]
322+ return [ProvenanceAsset ( payload , purl . name , url ) ]
310323 except LoadIntotoAttestationError as load_error :
311324 logger .error ("Error while loading provenance: %s" , load_error )
312325 return []
313326
314327
315328def find_provenance_from_ci (
316329 analyze_ctx : AnalyzeContext , git_obj : Git | None , download_path : str
317- ) -> InTotoPayload | None :
330+ ) -> ProvenanceAsset | None :
318331 """Try to find provenance from CI services of the repository.
319332
320333 Note that we stop going through the CI services once we encounter a CI service
@@ -409,7 +422,10 @@ def find_provenance_from_ci(
409422 download_provenances_from_ci_service (ci_info , download_path )
410423
411424 # TODO consider how to handle multiple payloads here.
412- return ci_info ["provenances" ][0 ].payload if ci_info ["provenances" ] else None
425+ if ci_info ["provenances" ]:
426+ provenance = ci_info ["provenances" ][0 ]
427+ return ProvenanceAsset (provenance .payload , provenance .asset .name , provenance .asset .url )
428+ return None
413429
414430 else :
415431 logger .debug ("CI service not supported for provenance finding: %s" , ci_service .name )
0 commit comments