scanoss 1.20.4__py3-none-any.whl → 1.20.6__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.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +41 -2
- scanoss/components.py +5 -3
- scanoss/data/build_date.txt +1 -1
- scanoss/scanner.py +25 -20
- scanoss/scanossapi.py +29 -7
- scanoss/scanossgrpc.py +117 -58
- scanoss/spdxlite.py +281 -30
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info}/METADATA +3 -2
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info}/RECORD +14 -14
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info}/WHEEL +1 -1
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info}/entry_points.txt +0 -0
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info/licenses}/LICENSE +0 -0
- {scanoss-1.20.4.dist-info → scanoss-1.20.6.dist-info}/top_level.txt +0 -0
scanoss/scanossgrpc.py
CHANGED
|
@@ -22,50 +22,58 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
import concurrent.futures
|
|
26
|
+
import json
|
|
25
27
|
import os
|
|
26
28
|
import uuid
|
|
29
|
+
from urllib.parse import urlparse
|
|
27
30
|
|
|
28
31
|
import grpc
|
|
29
|
-
import json
|
|
30
|
-
|
|
31
32
|
from google.protobuf.json_format import MessageToDict, ParseDict
|
|
32
33
|
from pypac.parser import PACFile
|
|
33
34
|
from pypac.resolver import ProxyResolver
|
|
34
|
-
from urllib.parse import urlparse
|
|
35
35
|
|
|
36
|
-
from .
|
|
37
|
-
from .api.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
from .api.common.v2.scanoss_common_pb2 import EchoRequest, EchoResponse, StatusResponse, StatusCode, PurlRequest
|
|
45
|
-
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityResponse
|
|
46
|
-
from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
|
|
36
|
+
from . import __version__
|
|
37
|
+
from .api.common.v2.scanoss_common_pb2 import (
|
|
38
|
+
EchoRequest,
|
|
39
|
+
EchoResponse,
|
|
40
|
+
PurlRequest,
|
|
41
|
+
StatusCode,
|
|
42
|
+
StatusResponse,
|
|
43
|
+
)
|
|
47
44
|
from .api.components.v2.scanoss_components_pb2 import (
|
|
48
45
|
CompSearchRequest,
|
|
49
46
|
CompSearchResponse,
|
|
50
47
|
CompVersionRequest,
|
|
51
48
|
CompVersionResponse,
|
|
52
49
|
)
|
|
50
|
+
from .api.components.v2.scanoss_components_pb2_grpc import ComponentsStub
|
|
51
|
+
from .api.cryptography.v2.scanoss_cryptography_pb2 import AlgorithmResponse
|
|
52
|
+
from .api.cryptography.v2.scanoss_cryptography_pb2_grpc import CryptographyStub
|
|
53
|
+
from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest
|
|
54
|
+
from .api.dependencies.v2.scanoss_dependencies_pb2_grpc import DependenciesStub
|
|
53
55
|
from .api.provenance.v2.scanoss_provenance_pb2 import ProvenanceResponse
|
|
56
|
+
from .api.provenance.v2.scanoss_provenance_pb2_grpc import ProvenanceStub
|
|
57
|
+
from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
|
|
58
|
+
from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub
|
|
59
|
+
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityResponse
|
|
60
|
+
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
|
|
54
61
|
from .scanossbase import ScanossBase
|
|
55
|
-
from . import __version__
|
|
56
62
|
|
|
57
63
|
DEFAULT_URL = 'https://api.osskb.org' # default free service URL
|
|
58
64
|
DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
|
|
59
65
|
SCANOSS_GRPC_URL = os.environ.get('SCANOSS_GRPC_URL') if os.environ.get('SCANOSS_GRPC_URL') else DEFAULT_URL
|
|
60
66
|
SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_API_KEY') else ''
|
|
61
67
|
|
|
68
|
+
MAX_CONCURRENT_REQUESTS = 5
|
|
69
|
+
|
|
62
70
|
|
|
63
71
|
class ScanossGrpc(ScanossBase):
|
|
64
72
|
"""
|
|
65
73
|
Client for gRPC functionality
|
|
66
74
|
"""
|
|
67
75
|
|
|
68
|
-
def __init__(
|
|
76
|
+
def __init__( # noqa: PLR0913, PLR0915
|
|
69
77
|
self,
|
|
70
78
|
url: str = None,
|
|
71
79
|
debug: bool = False,
|
|
@@ -78,6 +86,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
78
86
|
proxy: str = None,
|
|
79
87
|
grpc_proxy: str = None,
|
|
80
88
|
pac: PACFile = None,
|
|
89
|
+
req_headers: dict = None,
|
|
81
90
|
):
|
|
82
91
|
"""
|
|
83
92
|
|
|
@@ -95,22 +104,29 @@ class ScanossGrpc(ScanossBase):
|
|
|
95
104
|
grpc_proxy='http://<ip>:<port>'
|
|
96
105
|
"""
|
|
97
106
|
super().__init__(debug, trace, quiet)
|
|
98
|
-
self.url = url
|
|
107
|
+
self.url = url
|
|
99
108
|
self.api_key = api_key if api_key else SCANOSS_API_KEY
|
|
100
|
-
if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
|
|
101
|
-
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
102
|
-
self.url = self.url.lower()
|
|
103
|
-
self.orig_url = self.url # Used for proxy lookup
|
|
104
109
|
self.timeout = timeout
|
|
105
110
|
self.proxy = proxy
|
|
106
111
|
self.grpc_proxy = grpc_proxy
|
|
107
112
|
self.pac = pac
|
|
113
|
+
self.req_headers = req_headers
|
|
108
114
|
self.metadata = []
|
|
115
|
+
|
|
116
|
+
|
|
109
117
|
if self.api_key:
|
|
110
118
|
self.metadata.append(('x-api-key', api_key)) # Set API key if we have one
|
|
111
119
|
if ver_details:
|
|
112
120
|
self.metadata.append(('x-scanoss-client', ver_details))
|
|
113
121
|
self.metadata.append(('user-agent', f'scanoss-py/{__version__}'))
|
|
122
|
+
self.load_generic_headers()
|
|
123
|
+
|
|
124
|
+
self.url = url if url else SCANOSS_GRPC_URL
|
|
125
|
+
if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
|
|
126
|
+
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
127
|
+
self.url = self.url.lower()
|
|
128
|
+
self.orig_url = self.url # Used for proxy lookup
|
|
129
|
+
|
|
114
130
|
secure = True if self.url.startswith('https:') else False # Is it a secure connection?
|
|
115
131
|
if self.url.startswith('http'):
|
|
116
132
|
u = urlparse(self.url)
|
|
@@ -222,31 +238,54 @@ class ScanossGrpc(ScanossBase):
|
|
|
222
238
|
:return: Server response or None
|
|
223
239
|
"""
|
|
224
240
|
if not dependencies:
|
|
225
|
-
self.print_stderr(
|
|
241
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
226
242
|
return None
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
|
|
244
|
+
files_json = dependencies.get('files')
|
|
245
|
+
|
|
246
|
+
if files_json is None or len(files_json) == 0:
|
|
247
|
+
self.print_stderr('ERROR: No dependency data supplied to send to gRPC service.')
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
def process_file(file):
|
|
251
|
+
request_id = str(uuid.uuid4())
|
|
252
|
+
try:
|
|
253
|
+
file_request = {'files': [file]}
|
|
254
|
+
|
|
255
|
+
request = ParseDict(file_request, DependencyRequest())
|
|
256
|
+
request.depth = depth
|
|
257
|
+
metadata = self.metadata[:]
|
|
258
|
+
metadata.append(('x-request-id', request_id))
|
|
259
|
+
self.print_debug(f'Sending dependency data for decoration (rqId: {request_id})...')
|
|
260
|
+
resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout)
|
|
261
|
+
|
|
262
|
+
return MessageToDict(resp, preserving_proto_field_name=True)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
self.print_stderr(
|
|
265
|
+
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
266
|
+
)
|
|
233
267
|
return None
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
268
|
+
|
|
269
|
+
all_responses = []
|
|
270
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT_REQUESTS) as executor:
|
|
271
|
+
future_to_file = {executor.submit(process_file, file): file for file in files_json}
|
|
272
|
+
|
|
273
|
+
for future in concurrent.futures.as_completed(future_to_file):
|
|
274
|
+
response = future.result()
|
|
275
|
+
if response:
|
|
276
|
+
all_responses.append(response)
|
|
277
|
+
|
|
278
|
+
SUCCESS_STATUS = 'SUCCESS'
|
|
279
|
+
|
|
280
|
+
merged_response = {'files': [], 'status': {'status': SUCCESS_STATUS, 'message': 'Success'}}
|
|
281
|
+
for response in all_responses:
|
|
282
|
+
if response:
|
|
283
|
+
if 'files' in response and len(response['files']) > 0:
|
|
284
|
+
merged_response['files'].append(response['files'][0])
|
|
285
|
+
# Overwrite the status if the any of the responses was not successful
|
|
286
|
+
if 'status' in response and response['status']['status'] != SUCCESS_STATUS:
|
|
287
|
+
merged_response['status'] = response['status']
|
|
288
|
+
return merged_response
|
|
250
289
|
|
|
251
290
|
def get_crypto_json(self, purls: dict) -> dict:
|
|
252
291
|
"""
|
|
@@ -255,7 +294,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
255
294
|
:return: Server response or None
|
|
256
295
|
"""
|
|
257
296
|
if not purls:
|
|
258
|
-
self.print_stderr(
|
|
297
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
259
298
|
return None
|
|
260
299
|
request_id = str(uuid.uuid4())
|
|
261
300
|
resp: AlgorithmResponse
|
|
@@ -285,7 +324,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
285
324
|
:return: Server response or None
|
|
286
325
|
"""
|
|
287
326
|
if not purls:
|
|
288
|
-
self.print_stderr(
|
|
327
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
289
328
|
return None
|
|
290
329
|
request_id = str(uuid.uuid4())
|
|
291
330
|
resp: VulnerabilityResponse
|
|
@@ -315,7 +354,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
315
354
|
:return: Server response or None
|
|
316
355
|
"""
|
|
317
356
|
if not purls:
|
|
318
|
-
self.print_stderr(
|
|
357
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
319
358
|
return None
|
|
320
359
|
request_id = str(uuid.uuid4())
|
|
321
360
|
resp: SemgrepResponse
|
|
@@ -345,7 +384,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
345
384
|
:return: Server response or None
|
|
346
385
|
"""
|
|
347
386
|
if not search:
|
|
348
|
-
self.print_stderr(
|
|
387
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
349
388
|
return None
|
|
350
389
|
request_id = str(uuid.uuid4())
|
|
351
390
|
resp: CompSearchResponse
|
|
@@ -375,7 +414,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
375
414
|
:return: Server response or None
|
|
376
415
|
"""
|
|
377
416
|
if not search:
|
|
378
|
-
self.print_stderr(
|
|
417
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
379
418
|
return None
|
|
380
419
|
request_id = str(uuid.uuid4())
|
|
381
420
|
resp: CompVersionResponse
|
|
@@ -404,6 +443,10 @@ class ScanossGrpc(ScanossBase):
|
|
|
404
443
|
:param status_response: Status Response
|
|
405
444
|
:return: True if successful, False otherwise
|
|
406
445
|
"""
|
|
446
|
+
|
|
447
|
+
SUCCEDED_WITH_WARNINGS_STATUS_CODE = 2
|
|
448
|
+
FAILED_STATUS_CODE = 3
|
|
449
|
+
|
|
407
450
|
if not status_response:
|
|
408
451
|
self.print_stderr(f'Warning: No status response supplied (rqId: {request_id}). Assuming it was ok.')
|
|
409
452
|
return True
|
|
@@ -411,11 +454,11 @@ class ScanossGrpc(ScanossBase):
|
|
|
411
454
|
status_code: StatusCode = status_response.status
|
|
412
455
|
if status_code > 1:
|
|
413
456
|
ret_val = False # default to failed
|
|
414
|
-
msg =
|
|
415
|
-
if status_code ==
|
|
416
|
-
msg =
|
|
457
|
+
msg = 'Unsuccessful'
|
|
458
|
+
if status_code == SUCCEDED_WITH_WARNINGS_STATUS_CODE:
|
|
459
|
+
msg = 'Succeeded with warnings'
|
|
417
460
|
ret_val = True # No need to fail as it succeeded with warnings
|
|
418
|
-
elif status_code ==
|
|
461
|
+
elif status_code == FAILED_STATUS_CODE:
|
|
419
462
|
msg = 'Failed with warnings'
|
|
420
463
|
self.print_stderr(f'{msg} (rqId: {request_id} - status: {status_code}): {status_response.message}')
|
|
421
464
|
return ret_val
|
|
@@ -428,10 +471,10 @@ class ScanossGrpc(ScanossBase):
|
|
|
428
471
|
:param self:
|
|
429
472
|
"""
|
|
430
473
|
if self.grpc_proxy:
|
|
431
|
-
self.print_debug(
|
|
474
|
+
self.print_debug('Setting GRPC (grpc_proxy) proxy...')
|
|
432
475
|
os.environ['grpc_proxy'] = self.grpc_proxy
|
|
433
476
|
elif self.proxy:
|
|
434
|
-
self.print_debug(
|
|
477
|
+
self.print_debug('Setting GRPC (http_proxy/https_proxy) proxies...')
|
|
435
478
|
os.environ['http_proxy'] = self.proxy
|
|
436
479
|
os.environ['https_proxy'] = self.proxy
|
|
437
480
|
elif self.pac:
|
|
@@ -450,7 +493,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
450
493
|
:return: Server response or None
|
|
451
494
|
"""
|
|
452
495
|
if not purls:
|
|
453
|
-
self.print_stderr(
|
|
496
|
+
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
454
497
|
return None
|
|
455
498
|
request_id = str(uuid.uuid4())
|
|
456
499
|
resp: ProvenanceResponse
|
|
@@ -461,8 +504,9 @@ class ScanossGrpc(ScanossBase):
|
|
|
461
504
|
self.print_debug(f'Sending data for provenance decoration (rqId: {request_id})...')
|
|
462
505
|
resp = self.provenance_stub.GetComponentProvenance(request, metadata=metadata, timeout=self.timeout)
|
|
463
506
|
except Exception as e:
|
|
464
|
-
self.print_stderr(
|
|
465
|
-
|
|
507
|
+
self.print_stderr(
|
|
508
|
+
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
509
|
+
)
|
|
466
510
|
else:
|
|
467
511
|
if resp:
|
|
468
512
|
if not self._check_status_response(resp.status, request_id):
|
|
@@ -470,6 +514,21 @@ class ScanossGrpc(ScanossBase):
|
|
|
470
514
|
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
471
515
|
return resp_dict
|
|
472
516
|
return None
|
|
517
|
+
|
|
518
|
+
def load_generic_headers(self):
|
|
519
|
+
"""
|
|
520
|
+
Adds custom headers from req_headers to metadata.
|
|
521
|
+
|
|
522
|
+
If x-api-key is present and no URL is configured (directly or via
|
|
523
|
+
environment), sets URL to the premium endpoint (DEFAULT_URL2).
|
|
524
|
+
"""
|
|
525
|
+
if self.req_headers: # Load generic headers
|
|
526
|
+
for key, value in self.req_headers.items():
|
|
527
|
+
if key == 'x-api-key': # Set premium URL if x-api-key header is set
|
|
528
|
+
if not self.url and not os.environ.get('SCANOSS_GRPC_URL'):
|
|
529
|
+
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
530
|
+
self.api_key = value
|
|
531
|
+
self.metadata.append((key, value))
|
|
473
532
|
#
|
|
474
533
|
# End of ScanossGrpc Class
|
|
475
534
|
#
|