scanoss 1.27.1__py3-none-any.whl → 1.43.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +1 -1
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -47
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +32 -21
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +1000 -186
- scanoss/components.py +80 -50
- scanoss/constants.py +7 -1
- scanoss/cryptography.py +89 -55
- scanoss/csvoutput.py +13 -7
- scanoss/cyclonedx.py +141 -9
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +2 -163
- scanoss/filecount.py +37 -38
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/{policy_check.py → policy_check/policy_check.py} +65 -72
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
- scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +57 -71
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
- scanoss/osadl.py +125 -0
- scanoss/scanner.py +135 -253
- scanoss/scanners/folder_hasher.py +47 -32
- scanoss/scanners/scanner_hfh.py +50 -18
- scanoss/scanoss_settings.py +33 -3
- scanoss/scanossapi.py +23 -25
- scanoss/scanossbase.py +1 -1
- scanoss/scanossgrpc.py +543 -289
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +11 -4
- scanoss/threadeddependencies.py +19 -18
- scanoss/threadedscanning.py +10 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/winnowing.py +71 -19
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
- scanoss-1.43.1.dist-info/RECORD +110 -0
- scanoss/inspection/component_summary.py +0 -94
- scanoss-1.27.1.dist-info/RECORD +0 -87
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/components.py
CHANGED
|
@@ -29,6 +29,9 @@ from typing import List, Optional, TextIO
|
|
|
29
29
|
|
|
30
30
|
from pypac.parser import PACFile
|
|
31
31
|
|
|
32
|
+
from scanoss.cyclonedx import CycloneDx
|
|
33
|
+
from scanoss.utils.file import validate_json_file
|
|
34
|
+
|
|
32
35
|
from .scanner import Scanner
|
|
33
36
|
from .scanossbase import ScanossBase
|
|
34
37
|
from .scanossgrpc import ScanossGrpc
|
|
@@ -52,6 +55,8 @@ class Components(ScanossBase):
|
|
|
52
55
|
ca_cert: str = None,
|
|
53
56
|
pac: PACFile = None,
|
|
54
57
|
req_headers: dict = None,
|
|
58
|
+
ignore_cert_errors: bool = False,
|
|
59
|
+
use_grpc: bool = False,
|
|
55
60
|
):
|
|
56
61
|
"""
|
|
57
62
|
Handle all component style requests
|
|
@@ -66,9 +71,13 @@ class Components(ScanossBase):
|
|
|
66
71
|
:param grpc_proxy: Specific gRPC proxy (optional)
|
|
67
72
|
:param ca_cert: TLS client certificate (optional)
|
|
68
73
|
:param pac: Proxy Auto-Config file (optional)
|
|
74
|
+
:param req_headers: Additional headers to send with requests (optional)
|
|
75
|
+
:param ignore_cert_errors: Ignore TLS certificate errors (optional)
|
|
76
|
+
:param use_grpc: Use gRPC instead of HTTP (optional)
|
|
69
77
|
"""
|
|
70
78
|
super().__init__(debug, trace, quiet)
|
|
71
79
|
ver_details = Scanner.version_details()
|
|
80
|
+
self.use_grpc = use_grpc
|
|
72
81
|
self.grpc_api = ScanossGrpc(
|
|
73
82
|
url=grpc_url,
|
|
74
83
|
debug=debug,
|
|
@@ -82,26 +91,41 @@ class Components(ScanossBase):
|
|
|
82
91
|
grpc_proxy=grpc_proxy,
|
|
83
92
|
timeout=timeout,
|
|
84
93
|
req_headers=req_headers,
|
|
94
|
+
ignore_cert_errors=ignore_cert_errors,
|
|
85
95
|
)
|
|
96
|
+
self.cdx = CycloneDx(debug=self.debug)
|
|
97
|
+
|
|
98
|
+
def load_comps(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
|
|
99
|
+
"""
|
|
100
|
+
Load the specified components and return a dictionary
|
|
101
|
+
|
|
102
|
+
:param json_file: JSON Components file (optional)
|
|
103
|
+
:param purls: list pf PURLs (optional)
|
|
104
|
+
:return: Components Request dictionary or None
|
|
105
|
+
"""
|
|
106
|
+
return self.load_purls(json_file, purls, 'components')
|
|
86
107
|
|
|
87
|
-
def load_purls(
|
|
108
|
+
def load_purls(
|
|
109
|
+
self, json_file: Optional[str] = None, purls: Optional[List[str]] = None, field: str = 'purls'
|
|
110
|
+
) -> Optional[dict]:
|
|
88
111
|
"""
|
|
89
112
|
Load the specified purls and return a dictionary
|
|
90
113
|
|
|
91
114
|
:param json_file: JSON PURL file (optional)
|
|
92
115
|
:param purls: list of PURLs (optional)
|
|
116
|
+
:param field: Name of the dictionary field to store the purls in (default: 'purls')
|
|
93
117
|
:return: PURL Request dictionary or None
|
|
94
118
|
"""
|
|
95
119
|
if json_file:
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
result = validate_json_file(json_file)
|
|
121
|
+
if not result.is_valid:
|
|
122
|
+
self.print_stderr(f'ERROR: Problem parsing input JSON: {result.error}')
|
|
98
123
|
return None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return None
|
|
124
|
+
|
|
125
|
+
if self.cdx.is_cyclonedx_json(json.dumps(result.data)):
|
|
126
|
+
purl_request = self.cdx.get_purls_request_from_cdx(result.data, field)
|
|
127
|
+
else:
|
|
128
|
+
purl_request = result.data
|
|
105
129
|
elif purls:
|
|
106
130
|
if not all(isinstance(purl, str) for purl in purls):
|
|
107
131
|
self.print_stderr('ERROR: PURLs must be a list of strings.')
|
|
@@ -109,14 +133,14 @@ class Components(ScanossBase):
|
|
|
109
133
|
parsed_purls = []
|
|
110
134
|
for p in purls:
|
|
111
135
|
parsed_purls.append({'purl': p})
|
|
112
|
-
purl_request = {
|
|
136
|
+
purl_request = {field: parsed_purls}
|
|
113
137
|
else:
|
|
114
138
|
self.print_stderr('ERROR: No purls specified to process.')
|
|
115
139
|
return None
|
|
116
|
-
purl_count = len(purl_request.get(
|
|
117
|
-
self.print_debug(f'Parsed
|
|
140
|
+
purl_count = len(purl_request.get(field, []))
|
|
141
|
+
self.print_debug(f'Parsed {field} ({purl_count}): {purl_request}')
|
|
118
142
|
if purl_count == 0:
|
|
119
|
-
self.print_stderr('ERROR: No
|
|
143
|
+
self.print_stderr(f'ERROR: No {field} parsed from request.')
|
|
120
144
|
return None
|
|
121
145
|
return purl_request
|
|
122
146
|
|
|
@@ -142,8 +166,8 @@ class Components(ScanossBase):
|
|
|
142
166
|
"""
|
|
143
167
|
Open the given filename if requested, otherwise return STDOUT
|
|
144
168
|
|
|
145
|
-
:param filename:
|
|
146
|
-
:return:
|
|
169
|
+
:param filename: filename to open or None to return STDOUT
|
|
170
|
+
:return: file descriptor or None
|
|
147
171
|
"""
|
|
148
172
|
file = sys.stdout
|
|
149
173
|
if filename:
|
|
@@ -166,32 +190,6 @@ class Components(ScanossBase):
|
|
|
166
190
|
self.print_trace(f'Closing file: {filename}')
|
|
167
191
|
file.close()
|
|
168
192
|
|
|
169
|
-
def get_crypto_details(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
|
|
170
|
-
"""
|
|
171
|
-
Retrieve the cryptographic details for the supplied PURLs
|
|
172
|
-
|
|
173
|
-
:param json_file: PURL JSON request file (optional)
|
|
174
|
-
:param purls: PURL request array (optional)
|
|
175
|
-
:param output_file: output filename (optional). Default: STDOUT
|
|
176
|
-
:return: True on success, False otherwise
|
|
177
|
-
"""
|
|
178
|
-
success = False
|
|
179
|
-
purls_request = self.load_purls(json_file, purls)
|
|
180
|
-
if purls_request is None or len(purls_request) == 0:
|
|
181
|
-
return False
|
|
182
|
-
file = self._open_file_or_sdtout(output_file)
|
|
183
|
-
if file is None:
|
|
184
|
-
return False
|
|
185
|
-
self.print_msg('Sending PURLs to Crypto API for decoration...')
|
|
186
|
-
response = self.grpc_api.get_crypto_json(purls_request)
|
|
187
|
-
if response:
|
|
188
|
-
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
189
|
-
success = True
|
|
190
|
-
if output_file:
|
|
191
|
-
self.print_msg(f'Results written to: {output_file}')
|
|
192
|
-
self._close_file(output_file, file)
|
|
193
|
-
return success
|
|
194
|
-
|
|
195
193
|
def get_vulnerabilities(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
|
|
196
194
|
"""
|
|
197
195
|
Retrieve any vulnerabilities related to the given PURLs
|
|
@@ -202,14 +200,14 @@ class Components(ScanossBase):
|
|
|
202
200
|
:return: True on success, False otherwise
|
|
203
201
|
"""
|
|
204
202
|
success = False
|
|
205
|
-
purls_request = self.
|
|
203
|
+
purls_request = self.load_comps(json_file, purls)
|
|
206
204
|
if purls_request is None or len(purls_request) == 0:
|
|
207
205
|
return False
|
|
208
206
|
file = self._open_file_or_sdtout(output_file)
|
|
209
207
|
if file is None:
|
|
210
208
|
return False
|
|
211
209
|
self.print_msg('Sending PURLs to Vulnerability API for decoration...')
|
|
212
|
-
response = self.grpc_api.get_vulnerabilities_json(purls_request)
|
|
210
|
+
response = self.grpc_api.get_vulnerabilities_json(purls_request, use_grpc=self.use_grpc)
|
|
213
211
|
if response:
|
|
214
212
|
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
215
213
|
success = True
|
|
@@ -228,14 +226,14 @@ class Components(ScanossBase):
|
|
|
228
226
|
:return: True on success, False otherwise
|
|
229
227
|
"""
|
|
230
228
|
success = False
|
|
231
|
-
purls_request = self.
|
|
229
|
+
purls_request = self.load_comps(json_file, purls)
|
|
232
230
|
if purls_request is None or len(purls_request) == 0:
|
|
233
231
|
return False
|
|
234
232
|
file = self._open_file_or_sdtout(output_file)
|
|
235
233
|
if file is None:
|
|
236
234
|
return False
|
|
237
235
|
self.print_msg('Sending PURLs to Semgrep API for decoration...')
|
|
238
|
-
response = self.grpc_api.get_semgrep_json(purls_request)
|
|
236
|
+
response = self.grpc_api.get_semgrep_json(purls_request, use_grpc=self.use_grpc)
|
|
239
237
|
if response:
|
|
240
238
|
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
241
239
|
success = True
|
|
@@ -285,7 +283,7 @@ class Components(ScanossBase):
|
|
|
285
283
|
if file is None:
|
|
286
284
|
return False
|
|
287
285
|
self.print_msg('Sending search data to Components API...')
|
|
288
|
-
response = self.grpc_api.search_components_json(request)
|
|
286
|
+
response = self.grpc_api.search_components_json(request, use_grpc=self.use_grpc)
|
|
289
287
|
if response:
|
|
290
288
|
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
291
289
|
success = True
|
|
@@ -321,7 +319,7 @@ class Components(ScanossBase):
|
|
|
321
319
|
if file is None:
|
|
322
320
|
return False
|
|
323
321
|
self.print_msg('Sending PURLs to Component Versions API...')
|
|
324
|
-
response = self.grpc_api.get_component_versions_json(request)
|
|
322
|
+
response = self.grpc_api.get_component_versions_json(request, use_grpc=self.use_grpc)
|
|
325
323
|
if response:
|
|
326
324
|
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
327
325
|
success = True
|
|
@@ -346,7 +344,7 @@ class Components(ScanossBase):
|
|
|
346
344
|
bool: True on success, False otherwise
|
|
347
345
|
"""
|
|
348
346
|
success = False
|
|
349
|
-
purls_request = self.
|
|
347
|
+
purls_request = self.load_comps(json_file, purls)
|
|
350
348
|
if purls_request is None or len(purls_request) == 0:
|
|
351
349
|
return False
|
|
352
350
|
file = self._open_file_or_sdtout(output_file)
|
|
@@ -354,10 +352,42 @@ class Components(ScanossBase):
|
|
|
354
352
|
return False
|
|
355
353
|
if origin:
|
|
356
354
|
self.print_msg('Sending PURLs to Geo Provenance Origin API for decoration...')
|
|
357
|
-
response = self.grpc_api.get_provenance_origin(purls_request)
|
|
355
|
+
response = self.grpc_api.get_provenance_origin(purls_request, use_grpc=self.use_grpc)
|
|
358
356
|
else:
|
|
359
357
|
self.print_msg('Sending PURLs to Geo Provenance Declared API for decoration...')
|
|
360
|
-
response = self.grpc_api.get_provenance_json(purls_request)
|
|
358
|
+
response = self.grpc_api.get_provenance_json(purls_request, use_grpc=self.use_grpc)
|
|
359
|
+
if response:
|
|
360
|
+
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
361
|
+
success = True
|
|
362
|
+
if output_file:
|
|
363
|
+
self.print_msg(f'Results written to: {output_file}')
|
|
364
|
+
self._close_file(output_file, file)
|
|
365
|
+
return success
|
|
366
|
+
|
|
367
|
+
def get_licenses(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
|
|
368
|
+
"""
|
|
369
|
+
Retrieve the license details for the supplied PURLs
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
json_file (str, optional): Input JSON file. Defaults to None.
|
|
373
|
+
purls (None, optional): PURLs to retrieve license details for. Defaults to None.
|
|
374
|
+
output_file (str, optional): Output file. Defaults to None.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
bool: True on success, False otherwise
|
|
378
|
+
"""
|
|
379
|
+
success = False
|
|
380
|
+
|
|
381
|
+
purls_request = self.load_purls(json_file, purls)
|
|
382
|
+
if not purls_request:
|
|
383
|
+
return False
|
|
384
|
+
file = self._open_file_or_sdtout(output_file)
|
|
385
|
+
if file is None:
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
# We'll use the new ComponentBatchRequest instead of deprecated PurlRequest for the license api
|
|
389
|
+
component_batch_request = {'components': purls_request.get('purls')}
|
|
390
|
+
response = self.grpc_api.get_licenses(component_batch_request, use_grpc=self.use_grpc)
|
|
361
391
|
if response:
|
|
362
392
|
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
363
393
|
success = True
|
scanoss/constants.py
CHANGED
|
@@ -13,4 +13,10 @@ DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_API_TIMEOUT = 600
|
|
15
15
|
|
|
16
|
-
DEFAULT_HFH_RANK_THRESHOLD = 5
|
|
16
|
+
DEFAULT_HFH_RANK_THRESHOLD = 5
|
|
17
|
+
DEFAULT_HFH_DEPTH = 1
|
|
18
|
+
DEFAULT_HFH_RECURSIVE_THRESHOLD = 0.8
|
|
19
|
+
DEFAULT_HFH_MIN_ACCEPTED_SCORE = 0.15
|
|
20
|
+
|
|
21
|
+
VALID_LICENSE_SOURCES = ['component_declared', 'license_file', 'file_header', 'file_spdx_tag', 'scancode']
|
|
22
|
+
DEFAULT_COPYLEFT_LICENSE_SOURCES = ['component_declared', 'license_file']
|
scanoss/cryptography.py
CHANGED
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import Dict, List, Optional
|
|
4
4
|
|
|
5
|
+
from scanoss.cyclonedx import CycloneDx
|
|
5
6
|
from scanoss.scanossbase import ScanossBase
|
|
6
7
|
from scanoss.scanossgrpc import ScanossGrpc
|
|
7
8
|
from scanoss.utils.abstract_presenter import AbstractPresenter
|
|
@@ -18,14 +19,47 @@ MIN_SPLIT_PARTS = 2
|
|
|
18
19
|
@dataclass
|
|
19
20
|
class CryptographyConfig:
|
|
20
21
|
purl: List[str]
|
|
22
|
+
debug: bool = False
|
|
23
|
+
header: Optional[str] = None
|
|
21
24
|
input_file: Optional[str] = None
|
|
22
25
|
output_file: Optional[str] = None
|
|
23
|
-
header: Optional[str] = None
|
|
24
|
-
debug: bool = False
|
|
25
|
-
trace: bool = False
|
|
26
26
|
quiet: bool = False
|
|
27
|
+
trace: bool = False
|
|
28
|
+
use_grpc: bool = False
|
|
27
29
|
with_range: bool = False
|
|
28
30
|
|
|
31
|
+
def _process_input_file(self) -> dict:
|
|
32
|
+
"""
|
|
33
|
+
Process and validate the input file, returning the validated purl_request.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
dict: The validated purl_request dictionary
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ScanossCryptographyError: If the input file is invalid
|
|
40
|
+
"""
|
|
41
|
+
result = validate_json_file(self.input_file)
|
|
42
|
+
if not result.is_valid:
|
|
43
|
+
raise ScanossCryptographyError(
|
|
44
|
+
f'There was a problem with the purl input file. {result.error}'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
cdx = CycloneDx(debug=self.debug)
|
|
48
|
+
if cdx.is_cyclonedx_json(json.dumps(result.data)):
|
|
49
|
+
purl_request = cdx.get_purls_request_from_cdx(result.data)
|
|
50
|
+
else:
|
|
51
|
+
purl_request = result.data
|
|
52
|
+
|
|
53
|
+
if (
|
|
54
|
+
not isinstance(purl_request, dict)
|
|
55
|
+
or 'purls' not in purl_request
|
|
56
|
+
or not isinstance(purl_request['purls'], list)
|
|
57
|
+
or not all(isinstance(p, dict) and 'purl' in p for p in purl_request['purls'])
|
|
58
|
+
):
|
|
59
|
+
raise ScanossCryptographyError('The supplied input file is not in the correct PurlRequest format.')
|
|
60
|
+
|
|
61
|
+
return purl_request
|
|
62
|
+
|
|
29
63
|
def __post_init__(self):
|
|
30
64
|
"""
|
|
31
65
|
Validate that the configuration is valid.
|
|
@@ -36,46 +70,36 @@ class CryptographyConfig:
|
|
|
36
70
|
parts = purl.split('@')
|
|
37
71
|
if not (len(parts) >= MIN_SPLIT_PARTS and parts[1]):
|
|
38
72
|
raise ScanossCryptographyError(
|
|
39
|
-
f'Invalid PURL format: "{purl}".
|
|
73
|
+
f'Invalid PURL format: "{purl}".It must include a version (e.g., pkg:type/name@version)'
|
|
40
74
|
)
|
|
41
75
|
if self.input_file:
|
|
42
|
-
|
|
43
|
-
|
|
76
|
+
purl_request = self._process_input_file()
|
|
77
|
+
purls = purl_request['purls']
|
|
78
|
+
purls_with_requirement = []
|
|
79
|
+
if self.with_range and any('requirement' not in p for p in purls):
|
|
44
80
|
raise ScanossCryptographyError(
|
|
45
|
-
f'
|
|
81
|
+
f'One or more PURLs in "{self.input_file}" are missing the "requirement" field.'
|
|
46
82
|
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
or not all(isinstance(p, dict) and 'purl' in p for p in input_file_validation.data['purls'])
|
|
52
|
-
):
|
|
53
|
-
raise ScanossCryptographyError('The supplied input file is not in the correct PurlRequest format.')
|
|
54
|
-
purls = input_file_validation.data['purls']
|
|
55
|
-
purls_with_requirement = []
|
|
56
|
-
if self.with_range:
|
|
57
|
-
if any('requirement' not in p for p in purls):
|
|
58
|
-
raise ScanossCryptographyError(
|
|
59
|
-
f'One or more PURLs in "{self.input_file}" are missing the "requirement" field.'
|
|
60
|
-
)
|
|
83
|
+
|
|
84
|
+
for purl in purls:
|
|
85
|
+
if 'requirement' in purl:
|
|
86
|
+
purls_with_requirement.append(f'{purl["purl"]}@{purl["requirement"]}')
|
|
61
87
|
else:
|
|
62
|
-
|
|
63
|
-
purls_with_requirement.append(f'{purl["purl"]}@{purl["requirement"]}')
|
|
64
|
-
else:
|
|
65
|
-
purls_with_requirement = purls
|
|
88
|
+
purls_with_requirement.append(purl['purl'])
|
|
66
89
|
self.purl = purls_with_requirement
|
|
67
90
|
|
|
68
91
|
|
|
69
92
|
def create_cryptography_config_from_args(args) -> CryptographyConfig:
|
|
70
93
|
return CryptographyConfig(
|
|
71
94
|
debug=getattr(args, 'debug', False),
|
|
72
|
-
|
|
73
|
-
quiet=getattr(args, 'quiet', False),
|
|
74
|
-
with_range=getattr(args, 'with_range', False),
|
|
75
|
-
purl=getattr(args, 'purl', []),
|
|
95
|
+
header=getattr(args, 'header', None),
|
|
76
96
|
input_file=getattr(args, 'input', None),
|
|
77
97
|
output_file=getattr(args, 'output', None),
|
|
78
|
-
|
|
98
|
+
purl=getattr(args, 'purl', []),
|
|
99
|
+
quiet=getattr(args, 'quiet', False),
|
|
100
|
+
trace=getattr(args, 'trace', False),
|
|
101
|
+
use_grpc=getattr(args, 'grpc', False),
|
|
102
|
+
with_range=getattr(args, 'with_range', False),
|
|
79
103
|
)
|
|
80
104
|
|
|
81
105
|
|
|
@@ -112,7 +136,7 @@ class Cryptography:
|
|
|
112
136
|
|
|
113
137
|
self.client = client
|
|
114
138
|
self.config = config
|
|
115
|
-
self.
|
|
139
|
+
self.components_request = self._build_components_request()
|
|
116
140
|
self.results = None
|
|
117
141
|
|
|
118
142
|
def get_algorithms(self) -> Optional[Dict]:
|
|
@@ -123,15 +147,16 @@ class Cryptography:
|
|
|
123
147
|
Optional[Dict]: The folder hash response from the gRPC client, or None if an error occurs.
|
|
124
148
|
"""
|
|
125
149
|
|
|
126
|
-
if not self.
|
|
150
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
127
151
|
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
128
|
-
self.
|
|
129
|
-
|
|
130
|
-
)
|
|
152
|
+
components_str = ', '.join(p['purl'] for p in self.components_request['components'])
|
|
153
|
+
self.base.print_stderr(f'Getting cryptographic algorithms for {components_str}')
|
|
131
154
|
if self.config.with_range:
|
|
132
|
-
response = self.client.get_crypto_algorithms_in_range_for_purl(
|
|
155
|
+
response = self.client.get_crypto_algorithms_in_range_for_purl(
|
|
156
|
+
self.components_request, self.config.use_grpc
|
|
157
|
+
)
|
|
133
158
|
else:
|
|
134
|
-
response = self.client.get_crypto_algorithms_for_purl(self.
|
|
159
|
+
response = self.client.get_crypto_algorithms_for_purl(self.components_request, self.config.use_grpc)
|
|
135
160
|
if response:
|
|
136
161
|
self.results = response
|
|
137
162
|
|
|
@@ -145,17 +170,17 @@ class Cryptography:
|
|
|
145
170
|
Optional[Dict]: The encryption hints response from the gRPC client, or None if an error occurs.
|
|
146
171
|
"""
|
|
147
172
|
|
|
148
|
-
if not self.
|
|
173
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
149
174
|
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
150
175
|
self.base.print_stderr(
|
|
151
176
|
f'Getting encryption hints '
|
|
152
177
|
f'{"in range" if self.config.with_range else ""} '
|
|
153
|
-
f'for {", ".join([p["purl"] for p in self.
|
|
178
|
+
f'for {", ".join([p["purl"] for p in self.components_request["components"]])}'
|
|
154
179
|
)
|
|
155
180
|
if self.config.with_range:
|
|
156
|
-
response = self.client.get_encryption_hints_in_range_for_purl(self.
|
|
181
|
+
response = self.client.get_encryption_hints_in_range_for_purl(self.components_request, self.config.use_grpc)
|
|
157
182
|
else:
|
|
158
|
-
response = self.client.get_encryption_hints_for_purl(self.
|
|
183
|
+
response = self.client.get_encryption_hints_for_purl(self.components_request, self.config.use_grpc)
|
|
159
184
|
if response:
|
|
160
185
|
self.results = response
|
|
161
186
|
|
|
@@ -169,42 +194,51 @@ class Cryptography:
|
|
|
169
194
|
Optional[Dict]: The versions in range response from the gRPC client, or None if an error occurs.
|
|
170
195
|
"""
|
|
171
196
|
|
|
172
|
-
if not self.
|
|
197
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
173
198
|
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
174
199
|
|
|
175
|
-
self.
|
|
176
|
-
|
|
177
|
-
)
|
|
200
|
+
components_str = ', '.join(p['purl'] for p in self.components_request['components'])
|
|
201
|
+
self.base.print_stderr(f'Getting versions in range for {components_str}')
|
|
178
202
|
|
|
179
|
-
response = self.client.get_versions_in_range_for_purl(self.
|
|
203
|
+
response = self.client.get_versions_in_range_for_purl(self.components_request, self.config.use_grpc)
|
|
180
204
|
if response:
|
|
181
205
|
self.results = response
|
|
182
206
|
|
|
183
207
|
return self.results
|
|
184
208
|
|
|
185
|
-
def
|
|
209
|
+
def _build_components_request(
|
|
186
210
|
self,
|
|
187
211
|
) -> Optional[dict]:
|
|
188
212
|
"""
|
|
189
213
|
Load the specified purls from a JSON file or a list of PURLs and return a dictionary
|
|
190
214
|
|
|
191
|
-
Args:
|
|
192
|
-
json_file (Optional[str], optional): The JSON file containing the PURLs. Defaults to None.
|
|
193
|
-
purls (Optional[List[str]], optional): The list of PURLs. Defaults to None.
|
|
194
|
-
|
|
195
215
|
Returns:
|
|
196
216
|
Optional[dict]: The dictionary containing the PURLs
|
|
197
217
|
"""
|
|
198
218
|
return {
|
|
199
|
-
'
|
|
219
|
+
'components': [
|
|
200
220
|
{
|
|
201
|
-
'purl':
|
|
202
|
-
'requirement': self._extract_version_from_purl(
|
|
221
|
+
'purl': self._remove_version_from_purl(purl),
|
|
222
|
+
'requirement': self._extract_version_from_purl(purl),
|
|
203
223
|
}
|
|
204
|
-
for
|
|
224
|
+
for purl in self.config.purl
|
|
205
225
|
]
|
|
206
226
|
}
|
|
207
227
|
|
|
228
|
+
def _remove_version_from_purl(self, purl: str) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Remove version from purl
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
purl (str): The purl string to remove the version from
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
str: The purl string without the version
|
|
237
|
+
"""
|
|
238
|
+
if '@' not in purl:
|
|
239
|
+
return purl
|
|
240
|
+
return purl.split('@')[0]
|
|
241
|
+
|
|
208
242
|
def _extract_version_from_purl(self, purl: str) -> str:
|
|
209
243
|
"""
|
|
210
244
|
Extract version from purl
|
scanoss/csvoutput.py
CHANGED
|
@@ -21,11 +21,10 @@ SPDX-License-Identifier: MIT
|
|
|
21
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
-
|
|
24
|
+
import csv
|
|
25
25
|
import json
|
|
26
26
|
import os.path
|
|
27
27
|
import sys
|
|
28
|
-
import csv
|
|
29
28
|
|
|
30
29
|
from .scanossbase import ScanossBase
|
|
31
30
|
|
|
@@ -44,16 +43,20 @@ class CsvOutput(ScanossBase):
|
|
|
44
43
|
self.output_file = output_file
|
|
45
44
|
self.debug = debug
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
# TODO Refactor (fails linter)
|
|
47
|
+
def parse(self, data: json): #noqa PLR0912, PLR0915
|
|
48
48
|
"""
|
|
49
49
|
Parse the given input (raw/plain) JSON string and return CSV summary
|
|
50
50
|
:param data: json - JSON object
|
|
51
51
|
:return: CSV dictionary
|
|
52
52
|
"""
|
|
53
|
-
if
|
|
53
|
+
if data is None:
|
|
54
54
|
self.print_stderr('ERROR: No JSON data provided to parse.')
|
|
55
55
|
return None
|
|
56
|
-
|
|
56
|
+
if len(data) == 0:
|
|
57
|
+
self.print_msg('Warning: Empty scan results provided. Returning empty CSV list.')
|
|
58
|
+
return []
|
|
59
|
+
self.print_debug('Processing raw results into CSV format...')
|
|
57
60
|
csv_dict = []
|
|
58
61
|
row_id = 1
|
|
59
62
|
for f in data:
|
|
@@ -92,7 +95,8 @@ class CsvOutput(ScanossBase):
|
|
|
92
95
|
detected['licenses'] = ''
|
|
93
96
|
else:
|
|
94
97
|
detected['licenses'] = ';'.join(dc)
|
|
95
|
-
# inventory_id,path,usage,detected_component,detected_license,
|
|
98
|
+
# inventory_id,path,usage,detected_component,detected_license,
|
|
99
|
+
# detected_version,detected_latest,purl
|
|
96
100
|
csv_dict.append(
|
|
97
101
|
{
|
|
98
102
|
'inventory_id': row_id,
|
|
@@ -183,9 +187,11 @@ class CsvOutput(ScanossBase):
|
|
|
183
187
|
:return: True if successful, False otherwise
|
|
184
188
|
"""
|
|
185
189
|
csv_data = self.parse(data)
|
|
186
|
-
if
|
|
190
|
+
if csv_data is None:
|
|
187
191
|
self.print_stderr('ERROR: No CSV data returned for the JSON string provided.')
|
|
188
192
|
return False
|
|
193
|
+
if len(csv_data) == 0:
|
|
194
|
+
self.print_msg('Warning: Empty scan results - generating CSV with headers only.')
|
|
189
195
|
# Header row/column details
|
|
190
196
|
fields = [
|
|
191
197
|
'inventory_id',
|