scanoss 1.12.2__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/__init__.py +13 -13
- protoc_gen_swagger/options/__init__.py +13 -13
- 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 +18 -18
- scanoss/api/__init__.py +17 -17
- scanoss/api/common/__init__.py +17 -17
- scanoss/api/common/v2/__init__.py +17 -17
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- 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 -21
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
- scanoss/api/geoprovenance/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
- 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/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +2359 -370
- scanoss/components.py +187 -94
- scanoss/constants.py +22 -0
- scanoss/cryptography.py +308 -0
- scanoss/csvoutput.py +91 -58
- scanoss/cyclonedx.py +221 -63
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/data/scanoss-settings-schema.json +254 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +582 -0
- scanoss/filecount.py +75 -69
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/__init__.py +23 -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/policy_check.py +222 -0
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
- scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/summary/license_summary.py +191 -0
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +123 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/utils/scan_result_processor.py +417 -0
- scanoss/osadl.py +125 -0
- scanoss/results.py +275 -0
- scanoss/scancodedeps.py +87 -38
- scanoss/scanner.py +431 -539
- scanoss/scanners/__init__.py +23 -0
- scanoss/scanners/container_scanner.py +476 -0
- scanoss/scanners/folder_hasher.py +358 -0
- scanoss/scanners/scanner_config.py +73 -0
- scanoss/scanners/scanner_hfh.py +252 -0
- scanoss/scanoss_settings.py +337 -0
- scanoss/scanossapi.py +140 -101
- scanoss/scanossbase.py +59 -22
- scanoss/scanossgrpc.py +799 -251
- scanoss/scanpostprocessor.py +294 -0
- scanoss/scantype.py +22 -21
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +532 -174
- scanoss/threadeddependencies.py +148 -47
- scanoss/threadedscanning.py +53 -37
- scanoss/utils/__init__.py +23 -0
- scanoss/utils/abstract_presenter.py +103 -0
- scanoss/utils/crc64.py +96 -0
- scanoss/utils/file.py +84 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/utils/simhash.py +198 -0
- scanoss/winnowing.py +241 -63
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
- scanoss-1.43.1.dist-info/RECORD +110 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
- scanoss-1.12.2.dist-info/RECORD +0 -58
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/cryptography.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from scanoss.cyclonedx import CycloneDx
|
|
6
|
+
from scanoss.scanossbase import ScanossBase
|
|
7
|
+
from scanoss.scanossgrpc import ScanossGrpc
|
|
8
|
+
from scanoss.utils.abstract_presenter import AbstractPresenter
|
|
9
|
+
from scanoss.utils.file import validate_json_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ScanossCryptographyError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
MIN_SPLIT_PARTS = 2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CryptographyConfig:
|
|
21
|
+
purl: List[str]
|
|
22
|
+
debug: bool = False
|
|
23
|
+
header: Optional[str] = None
|
|
24
|
+
input_file: Optional[str] = None
|
|
25
|
+
output_file: Optional[str] = None
|
|
26
|
+
quiet: bool = False
|
|
27
|
+
trace: bool = False
|
|
28
|
+
use_grpc: bool = False
|
|
29
|
+
with_range: bool = False
|
|
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
|
+
|
|
63
|
+
def __post_init__(self):
|
|
64
|
+
"""
|
|
65
|
+
Validate that the configuration is valid.
|
|
66
|
+
"""
|
|
67
|
+
if self.purl:
|
|
68
|
+
if self.with_range:
|
|
69
|
+
for purl in self.purl:
|
|
70
|
+
parts = purl.split('@')
|
|
71
|
+
if not (len(parts) >= MIN_SPLIT_PARTS and parts[1]):
|
|
72
|
+
raise ScanossCryptographyError(
|
|
73
|
+
f'Invalid PURL format: "{purl}".It must include a version (e.g., pkg:type/name@version)'
|
|
74
|
+
)
|
|
75
|
+
if self.input_file:
|
|
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):
|
|
80
|
+
raise ScanossCryptographyError(
|
|
81
|
+
f'One or more PURLs in "{self.input_file}" are missing the "requirement" field.'
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
for purl in purls:
|
|
85
|
+
if 'requirement' in purl:
|
|
86
|
+
purls_with_requirement.append(f'{purl["purl"]}@{purl["requirement"]}')
|
|
87
|
+
else:
|
|
88
|
+
purls_with_requirement.append(purl['purl'])
|
|
89
|
+
self.purl = purls_with_requirement
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_cryptography_config_from_args(args) -> CryptographyConfig:
|
|
93
|
+
return CryptographyConfig(
|
|
94
|
+
debug=getattr(args, 'debug', False),
|
|
95
|
+
header=getattr(args, 'header', None),
|
|
96
|
+
input_file=getattr(args, 'input', None),
|
|
97
|
+
output_file=getattr(args, 'output', None),
|
|
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),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Cryptography:
|
|
107
|
+
"""
|
|
108
|
+
Cryptography Class
|
|
109
|
+
|
|
110
|
+
This class is used to decorate purls with cryptography information.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
config: CryptographyConfig,
|
|
116
|
+
client: ScanossGrpc,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Initialize the Cryptography.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
config (CryptographyConfig): Configuration parameters for the cryptography.
|
|
123
|
+
client (ScanossGrpc): gRPC client for communicating with the scanning service.
|
|
124
|
+
"""
|
|
125
|
+
self.base = ScanossBase(
|
|
126
|
+
debug=config.debug,
|
|
127
|
+
trace=config.trace,
|
|
128
|
+
quiet=config.quiet,
|
|
129
|
+
)
|
|
130
|
+
self.presenter = CryptographyPresenter(
|
|
131
|
+
self,
|
|
132
|
+
debug=config.debug,
|
|
133
|
+
trace=config.trace,
|
|
134
|
+
quiet=config.quiet,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self.client = client
|
|
138
|
+
self.config = config
|
|
139
|
+
self.components_request = self._build_components_request()
|
|
140
|
+
self.results = None
|
|
141
|
+
|
|
142
|
+
def get_algorithms(self) -> Optional[Dict]:
|
|
143
|
+
"""
|
|
144
|
+
Get the cryptographic algorithms for the provided purl or input file.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Optional[Dict]: The folder hash response from the gRPC client, or None if an error occurs.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
151
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
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}')
|
|
154
|
+
if self.config.with_range:
|
|
155
|
+
response = self.client.get_crypto_algorithms_in_range_for_purl(
|
|
156
|
+
self.components_request, self.config.use_grpc
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
response = self.client.get_crypto_algorithms_for_purl(self.components_request, self.config.use_grpc)
|
|
160
|
+
if response:
|
|
161
|
+
self.results = response
|
|
162
|
+
|
|
163
|
+
return self.results
|
|
164
|
+
|
|
165
|
+
def get_encryption_hints(self) -> Optional[Dict]:
|
|
166
|
+
"""
|
|
167
|
+
Get the encryption hints for the provided purl or input file.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Optional[Dict]: The encryption hints response from the gRPC client, or None if an error occurs.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
174
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
175
|
+
self.base.print_stderr(
|
|
176
|
+
f'Getting encryption hints '
|
|
177
|
+
f'{"in range" if self.config.with_range else ""} '
|
|
178
|
+
f'for {", ".join([p["purl"] for p in self.components_request["components"]])}'
|
|
179
|
+
)
|
|
180
|
+
if self.config.with_range:
|
|
181
|
+
response = self.client.get_encryption_hints_in_range_for_purl(self.components_request, self.config.use_grpc)
|
|
182
|
+
else:
|
|
183
|
+
response = self.client.get_encryption_hints_for_purl(self.components_request, self.config.use_grpc)
|
|
184
|
+
if response:
|
|
185
|
+
self.results = response
|
|
186
|
+
|
|
187
|
+
return self.results
|
|
188
|
+
|
|
189
|
+
def get_versions_in_range(self) -> Optional[Dict]:
|
|
190
|
+
"""
|
|
191
|
+
Given a list of PURLS and version ranges, get a list of versions that do/do not contain cryptographic algorithms
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Optional[Dict]: The versions in range response from the gRPC client, or None if an error occurs.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
if not self.components_request or not self.components_request.get('components'):
|
|
198
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
199
|
+
|
|
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}')
|
|
202
|
+
|
|
203
|
+
response = self.client.get_versions_in_range_for_purl(self.components_request, self.config.use_grpc)
|
|
204
|
+
if response:
|
|
205
|
+
self.results = response
|
|
206
|
+
|
|
207
|
+
return self.results
|
|
208
|
+
|
|
209
|
+
def _build_components_request(
|
|
210
|
+
self,
|
|
211
|
+
) -> Optional[dict]:
|
|
212
|
+
"""
|
|
213
|
+
Load the specified purls from a JSON file or a list of PURLs and return a dictionary
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Optional[dict]: The dictionary containing the PURLs
|
|
217
|
+
"""
|
|
218
|
+
return {
|
|
219
|
+
'components': [
|
|
220
|
+
{
|
|
221
|
+
'purl': self._remove_version_from_purl(purl),
|
|
222
|
+
'requirement': self._extract_version_from_purl(purl),
|
|
223
|
+
}
|
|
224
|
+
for purl in self.config.purl
|
|
225
|
+
]
|
|
226
|
+
}
|
|
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
|
+
|
|
242
|
+
def _extract_version_from_purl(self, purl: str) -> str:
|
|
243
|
+
"""
|
|
244
|
+
Extract version from purl
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
purl (str): The purl string to extract the version from
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
str: The extracted version
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ScanossCryptographyError: If the purl is not in the correct format
|
|
254
|
+
"""
|
|
255
|
+
try:
|
|
256
|
+
return purl.split('@')[-1]
|
|
257
|
+
except IndexError:
|
|
258
|
+
raise ScanossCryptographyError(f'Invalid purl format: {purl}')
|
|
259
|
+
|
|
260
|
+
def present(
|
|
261
|
+
self,
|
|
262
|
+
output_format: Optional[str] = None,
|
|
263
|
+
output_file: Optional[str] = None,
|
|
264
|
+
):
|
|
265
|
+
"""Present the results in the selected format"""
|
|
266
|
+
self.presenter.present(output_format=output_format, output_file=output_file)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class CryptographyPresenter(AbstractPresenter):
|
|
270
|
+
"""
|
|
271
|
+
Cryptography presenter class
|
|
272
|
+
Handles the presentation of the cryptography results
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
def __init__(self, cryptography: Cryptography, **kwargs):
|
|
276
|
+
super().__init__(**kwargs)
|
|
277
|
+
self.cryptography = cryptography
|
|
278
|
+
|
|
279
|
+
def _format_json_output(self) -> str:
|
|
280
|
+
"""
|
|
281
|
+
Format the scan output data into a JSON object
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
str: The formatted JSON string
|
|
285
|
+
"""
|
|
286
|
+
return json.dumps(self.cryptography.results, indent=2)
|
|
287
|
+
|
|
288
|
+
def _format_plain_output(self) -> str:
|
|
289
|
+
"""
|
|
290
|
+
Format the scan output data into a plain text string
|
|
291
|
+
"""
|
|
292
|
+
return (
|
|
293
|
+
json.dumps(self.cryptography.results, indent=2)
|
|
294
|
+
if isinstance(self.cryptography.results, dict)
|
|
295
|
+
else str(self.cryptography.results)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def _format_cyclonedx_output(self) -> str:
|
|
299
|
+
raise NotImplementedError('CycloneDX output is not implemented')
|
|
300
|
+
|
|
301
|
+
def _format_spdxlite_output(self) -> str:
|
|
302
|
+
raise NotImplementedError('SPDXlite output is not implemented')
|
|
303
|
+
|
|
304
|
+
def _format_csv_output(self) -> str:
|
|
305
|
+
raise NotImplementedError('CSV output is not implemented')
|
|
306
|
+
|
|
307
|
+
def _format_raw_output(self) -> str:
|
|
308
|
+
raise NotImplementedError('Raw output is not implemented')
|
scanoss/csvoutput.py
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Copyright (c) 2022, SCANOSS
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
+
import csv
|
|
24
25
|
import json
|
|
25
26
|
import os.path
|
|
26
27
|
import sys
|
|
27
|
-
import csv
|
|
28
28
|
|
|
29
29
|
from .scanossbase import ScanossBase
|
|
30
30
|
|
|
@@ -43,37 +43,41 @@ class CsvOutput(ScanossBase):
|
|
|
43
43
|
self.output_file = output_file
|
|
44
44
|
self.debug = debug
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
# TODO Refactor (fails linter)
|
|
47
|
+
def parse(self, data: json): #noqa PLR0912, PLR0915
|
|
47
48
|
"""
|
|
48
49
|
Parse the given input (raw/plain) JSON string and return CSV summary
|
|
49
50
|
:param data: json - JSON object
|
|
50
51
|
:return: CSV dictionary
|
|
51
52
|
"""
|
|
52
|
-
if
|
|
53
|
+
if data is None:
|
|
53
54
|
self.print_stderr('ERROR: No JSON data provided to parse.')
|
|
54
55
|
return None
|
|
55
|
-
|
|
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...')
|
|
56
60
|
csv_dict = []
|
|
57
61
|
row_id = 1
|
|
58
62
|
for f in data:
|
|
59
63
|
file_details = data.get(f)
|
|
60
64
|
# print(f'File: {f}: {file_details}')
|
|
61
65
|
for d in file_details:
|
|
62
|
-
id_details = d.get(
|
|
66
|
+
id_details = d.get('id')
|
|
63
67
|
if not id_details or id_details == 'none':
|
|
64
68
|
continue
|
|
65
|
-
matched = d.get(
|
|
66
|
-
lines = d.get(
|
|
67
|
-
oss_lines = d.get(
|
|
69
|
+
matched = d.get('matched', '')
|
|
70
|
+
lines = d.get('lines', '').replace(',', ';') # swap comma with semicolon to help basic parsers
|
|
71
|
+
oss_lines = d.get('oss_lines', '').replace(',', ';')
|
|
68
72
|
detected = {}
|
|
69
73
|
if id_details == 'dependency':
|
|
70
|
-
dependencies = d.get(
|
|
74
|
+
dependencies = d.get('dependencies')
|
|
71
75
|
if not dependencies:
|
|
72
76
|
self.print_stderr(f'Warning: No Dependencies found for {f}: {file_details}')
|
|
73
77
|
continue
|
|
74
78
|
for deps in dependencies:
|
|
75
79
|
detected = {}
|
|
76
|
-
purl = deps.get(
|
|
80
|
+
purl = deps.get('purl')
|
|
77
81
|
if not purl:
|
|
78
82
|
self.print_stderr(f'Warning: No PURL found for {f}: {deps}')
|
|
79
83
|
continue
|
|
@@ -84,25 +88,32 @@ class CsvOutput(ScanossBase):
|
|
|
84
88
|
dc = []
|
|
85
89
|
if licenses:
|
|
86
90
|
for lic in licenses:
|
|
87
|
-
name = lic.get(
|
|
91
|
+
name = lic.get('name')
|
|
88
92
|
if name and name not in dc: # Only save the license name once
|
|
89
93
|
dc.append(name)
|
|
90
94
|
if not dc or len(dc) == 0:
|
|
91
95
|
detected['licenses'] = ''
|
|
92
96
|
else:
|
|
93
97
|
detected['licenses'] = ';'.join(dc)
|
|
94
|
-
# inventory_id,path,usage,detected_component,detected_license,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
# inventory_id,path,usage,detected_component,detected_license,
|
|
99
|
+
# detected_version,detected_latest,purl
|
|
100
|
+
csv_dict.append(
|
|
101
|
+
{
|
|
102
|
+
'inventory_id': row_id,
|
|
103
|
+
'path': f,
|
|
104
|
+
'detected_usage': id_details,
|
|
105
|
+
'detected_component': detected.get('component'),
|
|
106
|
+
'detected_license': detected.get('licenses'),
|
|
107
|
+
'detected_version': detected.get('version'),
|
|
108
|
+
'detected_latest': detected.get('latest'),
|
|
109
|
+
'detected_purls': detected.get('purls'),
|
|
110
|
+
'detected_url': detected.get('url'),
|
|
111
|
+
'detected_path': detected.get('file', ''),
|
|
112
|
+
'detected_match': matched,
|
|
113
|
+
'detected_lines': lines,
|
|
114
|
+
'detected_oss_lines': oss_lines,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
106
117
|
row_id = row_id + 1
|
|
107
118
|
else:
|
|
108
119
|
purls = d.get('purl')
|
|
@@ -123,25 +134,31 @@ class CsvOutput(ScanossBase):
|
|
|
123
134
|
dc = []
|
|
124
135
|
if licenses:
|
|
125
136
|
for lic in licenses:
|
|
126
|
-
name = lic.get(
|
|
137
|
+
name = lic.get('name')
|
|
127
138
|
if name and name not in dc: # Only save the license name once
|
|
128
|
-
dc.append(lic.get(
|
|
139
|
+
dc.append(lic.get('name'))
|
|
129
140
|
if not dc or len(dc) == 0:
|
|
130
141
|
detected['licenses'] = ''
|
|
131
142
|
else:
|
|
132
143
|
detected['licenses'] = ';'.join(dc)
|
|
133
144
|
# inventory_id,path,usage,detected_component,detected_license,detected_version,detected_latest,purl
|
|
134
|
-
csv_dict.append(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
csv_dict.append(
|
|
146
|
+
{
|
|
147
|
+
'inventory_id': row_id,
|
|
148
|
+
'path': f,
|
|
149
|
+
'detected_usage': id_details,
|
|
150
|
+
'detected_component': detected.get('component'),
|
|
151
|
+
'detected_license': detected.get('licenses'),
|
|
152
|
+
'detected_version': detected.get('version'),
|
|
153
|
+
'detected_latest': detected.get('latest'),
|
|
154
|
+
'detected_purls': detected.get('purls'),
|
|
155
|
+
'detected_url': detected.get('url'),
|
|
156
|
+
'detected_path': detected.get('file', ''),
|
|
157
|
+
'detected_match': matched,
|
|
158
|
+
'detected_lines': lines,
|
|
159
|
+
'detected_oss_lines': oss_lines,
|
|
160
|
+
}
|
|
161
|
+
)
|
|
145
162
|
row_id = row_id + 1
|
|
146
163
|
return csv_dict
|
|
147
164
|
|
|
@@ -170,20 +187,34 @@ class CsvOutput(ScanossBase):
|
|
|
170
187
|
:return: True if successful, False otherwise
|
|
171
188
|
"""
|
|
172
189
|
csv_data = self.parse(data)
|
|
173
|
-
if
|
|
190
|
+
if csv_data is None:
|
|
174
191
|
self.print_stderr('ERROR: No CSV data returned for the JSON string provided.')
|
|
175
192
|
return False
|
|
193
|
+
if len(csv_data) == 0:
|
|
194
|
+
self.print_msg('Warning: Empty scan results - generating CSV with headers only.')
|
|
176
195
|
# Header row/column details
|
|
177
|
-
fields = [
|
|
178
|
-
|
|
179
|
-
|
|
196
|
+
fields = [
|
|
197
|
+
'inventory_id',
|
|
198
|
+
'path',
|
|
199
|
+
'detected_usage',
|
|
200
|
+
'detected_component',
|
|
201
|
+
'detected_license',
|
|
202
|
+
'detected_version',
|
|
203
|
+
'detected_latest',
|
|
204
|
+
'detected_purls',
|
|
205
|
+
'detected_url',
|
|
206
|
+
'detected_match',
|
|
207
|
+
'detected_lines',
|
|
208
|
+
'detected_oss_lines',
|
|
209
|
+
'detected_path',
|
|
210
|
+
]
|
|
180
211
|
file = sys.stdout
|
|
181
212
|
if not output_file and self.output_file:
|
|
182
213
|
output_file = self.output_file
|
|
183
214
|
if output_file:
|
|
184
215
|
file = open(output_file, 'w')
|
|
185
216
|
writer = csv.DictWriter(file, fieldnames=fields)
|
|
186
|
-
writer.writeheader()
|
|
217
|
+
writer.writeheader() # writing headers (field names)
|
|
187
218
|
writer.writerows(csv_data) # writing data rows
|
|
188
219
|
if output_file:
|
|
189
220
|
file.close()
|
|
@@ -206,6 +237,8 @@ class CsvOutput(ScanossBase):
|
|
|
206
237
|
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
207
238
|
return False
|
|
208
239
|
return self.produce_from_json(data, output_file)
|
|
240
|
+
|
|
241
|
+
|
|
209
242
|
#
|
|
210
243
|
# End of CsvOutput Class
|
|
211
244
|
#
|