scanoss 1.32.0__py3-none-any.whl → 1.34.0__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 +8 -6
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +5 -1
- scanoss/api/components/v2/scanoss_components_pb2.py +46 -32
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +6 -6
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +107 -29
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +545 -9
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +29 -21
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +1 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +51 -19
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +189 -1
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +27 -27
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +18 -18
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +29 -13
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +102 -8
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +21 -21
- scanoss/cli.py +193 -146
- scanoss/components.py +57 -46
- scanoss/cryptography.py +64 -44
- scanoss/cyclonedx.py +22 -0
- scanoss/data/build_date.txt +1 -1
- scanoss/scanossgrpc.py +433 -314
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/METADATA +4 -3
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/RECORD +34 -32
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/WHEEL +0 -0
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/top_level.txt +0 -0
scanoss/scanossgrpc.py
CHANGED
|
@@ -24,14 +24,13 @@ SPDX-License-Identifier: MIT
|
|
|
24
24
|
|
|
25
25
|
import concurrent.futures
|
|
26
26
|
import http.client as http_client
|
|
27
|
-
import json
|
|
28
27
|
import logging
|
|
29
28
|
import os
|
|
30
29
|
import sys
|
|
31
30
|
import time
|
|
32
31
|
import uuid
|
|
33
32
|
from dataclasses import dataclass
|
|
34
|
-
from enum import IntEnum
|
|
33
|
+
from enum import Enum, IntEnum
|
|
35
34
|
from typing import Dict, Optional
|
|
36
35
|
from urllib.parse import urlparse
|
|
37
36
|
|
|
@@ -44,6 +43,7 @@ from pypac.parser import PACFile
|
|
|
44
43
|
from pypac.resolver import ProxyResolver
|
|
45
44
|
from urllib3.exceptions import InsecureRequestWarning
|
|
46
45
|
|
|
46
|
+
from scanoss.api.licenses.v2.scanoss_licenses_pb2_grpc import LicenseStub
|
|
47
47
|
from scanoss.api.scanning.v2.scanoss_scanning_pb2_grpc import ScanningStub
|
|
48
48
|
from scanoss.constants import DEFAULT_TIMEOUT
|
|
49
49
|
|
|
@@ -51,27 +51,20 @@ from . import __version__
|
|
|
51
51
|
from .api.common.v2.scanoss_common_pb2 import (
|
|
52
52
|
ComponentsRequest,
|
|
53
53
|
EchoRequest,
|
|
54
|
-
EchoResponse,
|
|
55
|
-
PurlRequest,
|
|
56
54
|
StatusCode,
|
|
57
55
|
StatusResponse,
|
|
58
56
|
)
|
|
59
57
|
from .api.components.v2.scanoss_components_pb2 import (
|
|
60
58
|
CompSearchRequest,
|
|
61
|
-
CompSearchResponse,
|
|
62
59
|
CompVersionRequest,
|
|
63
|
-
CompVersionResponse,
|
|
64
60
|
)
|
|
65
61
|
from .api.components.v2.scanoss_components_pb2_grpc import ComponentsStub
|
|
66
62
|
from .api.cryptography.v2.scanoss_cryptography_pb2_grpc import CryptographyStub
|
|
67
63
|
from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest
|
|
68
64
|
from .api.dependencies.v2.scanoss_dependencies_pb2_grpc import DependenciesStub
|
|
69
|
-
from .api.geoprovenance.v2.scanoss_geoprovenance_pb2 import ContributorResponse
|
|
70
65
|
from .api.geoprovenance.v2.scanoss_geoprovenance_pb2_grpc import GeoProvenanceStub
|
|
71
66
|
from .api.scanning.v2.scanoss_scanning_pb2 import HFHRequest
|
|
72
|
-
from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
|
|
73
67
|
from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub
|
|
74
|
-
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import ComponentsVulnerabilityResponse
|
|
75
68
|
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
|
|
76
69
|
from .scanossbase import ScanossBase
|
|
77
70
|
|
|
@@ -81,24 +74,66 @@ SCANOSS_GRPC_URL = os.environ.get('SCANOSS_GRPC_URL') if os.environ.get('SCANOSS
|
|
|
81
74
|
SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_API_KEY') else ''
|
|
82
75
|
DEFAULT_URI_PREFIX = '/v2'
|
|
83
76
|
|
|
84
|
-
MAX_CONCURRENT_REQUESTS = 5
|
|
77
|
+
MAX_CONCURRENT_REQUESTS = 5 # Maximum number of concurrent requests to make
|
|
78
|
+
|
|
79
|
+
# REST API endpoint mappings with HTTP methods
|
|
80
|
+
REST_ENDPOINTS = {
|
|
81
|
+
'vulnerabilities.GetComponentsVulnerabilities': {'path': '/vulnerabilities/components', 'method': 'POST'},
|
|
82
|
+
'dependencies.Echo': {'path': '/dependencies/echo', 'method': 'POST'},
|
|
83
|
+
'dependencies.GetDependencies': {'path': '/dependencies/dependencies', 'method': 'POST'},
|
|
84
|
+
'cryptography.Echo': {'path': '/cryptography/echo', 'method': 'POST'},
|
|
85
|
+
'cryptography.GetComponentsAlgorithms': {'path': '/cryptography/algorithms/components', 'method': 'POST'},
|
|
86
|
+
'cryptography.GetComponentsAlgorithmsInRange': {
|
|
87
|
+
'path': '/cryptography/algorithms/range/components',
|
|
88
|
+
'method': 'POST',
|
|
89
|
+
},
|
|
90
|
+
'cryptography.GetComponentsEncryptionHints': {'path': '/cryptography/hints/components', 'method': 'POST'},
|
|
91
|
+
'cryptography.GetComponentsHintsInRange': {'path': '/cryptography/hints/components/range', 'method': 'POST'},
|
|
92
|
+
'cryptography.GetComponentsVersionsInRange': {
|
|
93
|
+
'path': '/cryptography/algorithms/versions/range/components',
|
|
94
|
+
'method': 'POST',
|
|
95
|
+
},
|
|
96
|
+
'components.SearchComponents': {'path': '/components/search', 'method': 'GET'},
|
|
97
|
+
'components.GetComponentVersions': {'path': '/components/versions', 'method': 'GET'},
|
|
98
|
+
'geoprovenance.GetCountryContributorsByComponents': {
|
|
99
|
+
'path': '/geoprovenance/countries/components',
|
|
100
|
+
'method': 'POST',
|
|
101
|
+
},
|
|
102
|
+
'geoprovenance.GetOriginByComponents': {'path': '/geoprovenance/origin/components', 'method': 'POST'},
|
|
103
|
+
'licenses.GetComponentsLicenses': {'path': '/licenses/components', 'method': 'POST'},
|
|
104
|
+
'semgrep.GetComponentsIssues': {'path': '/semgrep/issues/components', 'method': 'POST'},
|
|
105
|
+
'scanning.FolderHashScan': {'path': '/scanning/hfh/scan', 'method': 'POST'},
|
|
106
|
+
}
|
|
85
107
|
|
|
86
108
|
|
|
87
109
|
class ScanossGrpcError(Exception):
|
|
88
110
|
"""
|
|
89
111
|
Custom exception for SCANOSS gRPC errors
|
|
90
112
|
"""
|
|
113
|
+
|
|
91
114
|
pass
|
|
92
115
|
|
|
93
116
|
|
|
94
117
|
class ScanossGrpcStatusCode(IntEnum):
|
|
95
118
|
"""Status codes for SCANOSS gRPC responses"""
|
|
119
|
+
|
|
120
|
+
UNSPECIFIED = 0
|
|
96
121
|
SUCCESS = 1
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
SUCCEEDED_WITH_WARNINGS = 2
|
|
123
|
+
WARNING = 3
|
|
99
124
|
FAILED = 4
|
|
100
125
|
|
|
101
126
|
|
|
127
|
+
class ScanossRESTStatusCode(Enum):
|
|
128
|
+
"""Status codes for SCANOSS REST responses"""
|
|
129
|
+
|
|
130
|
+
UNSPECIFIED = 'UNSPECIFIED'
|
|
131
|
+
SUCCESS = 'SUCCESS'
|
|
132
|
+
SUCCEEDED_WITH_WARNINGS = 'SUCCEEDED_WITH_WARNINGS'
|
|
133
|
+
WARNING = 'WARNING'
|
|
134
|
+
FAILED = 'FAILED'
|
|
135
|
+
|
|
136
|
+
|
|
102
137
|
class ScanossGrpc(ScanossBase):
|
|
103
138
|
"""
|
|
104
139
|
Client for gRPC functionality
|
|
@@ -106,20 +141,20 @@ class ScanossGrpc(ScanossBase):
|
|
|
106
141
|
|
|
107
142
|
def __init__( # noqa: PLR0912, PLR0913, PLR0915
|
|
108
143
|
self,
|
|
109
|
-
url: str = None,
|
|
144
|
+
url: Optional[str] = None,
|
|
110
145
|
debug: bool = False,
|
|
111
146
|
trace: bool = False,
|
|
112
147
|
quiet: bool = False,
|
|
113
|
-
ca_cert: str = None,
|
|
114
|
-
api_key: str = None,
|
|
115
|
-
ver_details: str = None,
|
|
148
|
+
ca_cert: Optional[str] = None,
|
|
149
|
+
api_key: Optional[str] = None,
|
|
150
|
+
ver_details: Optional[str] = None,
|
|
116
151
|
timeout: int = 600,
|
|
117
|
-
proxy: str = None,
|
|
118
|
-
grpc_proxy: str = None,
|
|
119
|
-
pac: PACFile = None,
|
|
120
|
-
req_headers: dict = None,
|
|
152
|
+
proxy: Optional[str] = None,
|
|
153
|
+
grpc_proxy: Optional[str] = None,
|
|
154
|
+
pac: Optional[PACFile] = None,
|
|
155
|
+
req_headers: Optional[dict] = None,
|
|
121
156
|
ignore_cert_errors: bool = False,
|
|
122
|
-
use_grpc: bool = False,
|
|
157
|
+
use_grpc: Optional[bool] = False,
|
|
123
158
|
):
|
|
124
159
|
"""
|
|
125
160
|
|
|
@@ -208,6 +243,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
208
243
|
self.vuln_stub = VulnerabilitiesStub(grpc.insecure_channel(self.url))
|
|
209
244
|
self.provenance_stub = GeoProvenanceStub(grpc.insecure_channel(self.url))
|
|
210
245
|
self.scanning_stub = ScanningStub(grpc.insecure_channel(self.url))
|
|
246
|
+
self.license_stub = LicenseStub(grpc.insecure_channel(self.url))
|
|
211
247
|
else:
|
|
212
248
|
if ca_cert is not None:
|
|
213
249
|
credentials = grpc.ssl_channel_credentials(cert_data) # secure with specified certificate
|
|
@@ -220,59 +256,32 @@ class ScanossGrpc(ScanossBase):
|
|
|
220
256
|
self.vuln_stub = VulnerabilitiesStub(grpc.secure_channel(self.url, credentials))
|
|
221
257
|
self.provenance_stub = GeoProvenanceStub(grpc.secure_channel(self.url, credentials))
|
|
222
258
|
self.scanning_stub = ScanningStub(grpc.secure_channel(self.url, credentials))
|
|
259
|
+
self.license_stub = LicenseStub(grpc.secure_channel(self.url, credentials))
|
|
223
260
|
|
|
224
261
|
@classmethod
|
|
225
262
|
def _load_cert(cls, cert_file: str) -> bytes:
|
|
226
263
|
with open(cert_file, 'rb') as f:
|
|
227
264
|
return f.read()
|
|
228
265
|
|
|
229
|
-
def deps_echo(self, message: str = 'Hello there!') ->
|
|
266
|
+
def deps_echo(self, message: str = 'Hello there!') -> Optional[dict]:
|
|
230
267
|
"""
|
|
231
268
|
Send Echo message to the Dependency service
|
|
232
269
|
:param self:
|
|
233
270
|
:param message: Message to send (default: Hello there!)
|
|
234
271
|
:return: echo or None
|
|
235
272
|
"""
|
|
236
|
-
|
|
237
|
-
resp: EchoResponse
|
|
238
|
-
try:
|
|
239
|
-
metadata = self.metadata[:]
|
|
240
|
-
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
241
|
-
resp = self.dependencies_stub.Echo(EchoRequest(message=message), metadata=metadata, timeout=3)
|
|
242
|
-
except Exception as e:
|
|
243
|
-
self.print_stderr(
|
|
244
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
245
|
-
)
|
|
246
|
-
else:
|
|
247
|
-
if resp:
|
|
248
|
-
return resp.message
|
|
249
|
-
self.print_stderr(f'ERROR: Problem sending Echo request ({message}) to {self.url}. rqId: {request_id}')
|
|
250
|
-
return None
|
|
273
|
+
return self._call_api('dependencies.Echo', self.dependencies_stub.Echo, {'message': message}, EchoRequest)
|
|
251
274
|
|
|
252
|
-
def crypto_echo(self, message: str = 'Hello there!') ->
|
|
275
|
+
def crypto_echo(self, message: str = 'Hello there!') -> Optional[dict]:
|
|
253
276
|
"""
|
|
254
277
|
Send Echo message to the Cryptography service
|
|
255
278
|
:param self:
|
|
256
279
|
:param message: Message to send (default: Hello there!)
|
|
257
280
|
:return: echo or None
|
|
258
281
|
"""
|
|
259
|
-
|
|
260
|
-
resp: EchoResponse
|
|
261
|
-
try:
|
|
262
|
-
metadata = self.metadata[:]
|
|
263
|
-
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
264
|
-
resp = self.crypto_stub.Echo(EchoRequest(message=message), metadata=metadata, timeout=3)
|
|
265
|
-
except Exception as e:
|
|
266
|
-
self.print_stderr(
|
|
267
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
268
|
-
)
|
|
269
|
-
else:
|
|
270
|
-
if resp:
|
|
271
|
-
return resp.message
|
|
272
|
-
self.print_stderr(f'ERROR: Problem sending Echo request ({message}) to {self.url}. rqId: {request_id}')
|
|
273
|
-
return None
|
|
282
|
+
return self._call_api('cryptography.Echo', self.crypto_stub.Echo, {'message': message}, EchoRequest)
|
|
274
283
|
|
|
275
|
-
def get_dependencies(self, dependencies:
|
|
284
|
+
def get_dependencies(self, dependencies: Optional[dict] = None, depth: int = 1) -> Optional[dict]:
|
|
276
285
|
if not dependencies:
|
|
277
286
|
self.print_stderr('ERROR: No dependency data supplied to submit to the API.')
|
|
278
287
|
return None
|
|
@@ -281,7 +290,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
281
290
|
self.print_stderr(f'ERROR: No response for dependency request: {dependencies}')
|
|
282
291
|
return resp
|
|
283
292
|
|
|
284
|
-
def get_dependencies_json(self, dependencies: dict, depth: int = 1) -> dict:
|
|
293
|
+
def get_dependencies_json(self, dependencies: dict, depth: int = 1) -> Optional[dict]:
|
|
285
294
|
"""
|
|
286
295
|
Client function to call the rpc for GetDependencies
|
|
287
296
|
:param dependencies: Message to send to the service
|
|
@@ -296,11 +305,11 @@ class ScanossGrpc(ScanossBase):
|
|
|
296
305
|
self.print_stderr('ERROR: No dependency data supplied to send to decoration service.')
|
|
297
306
|
return None
|
|
298
307
|
all_responses = []
|
|
299
|
-
# determine if we are using gRPC or REST based on the use_grpc flag
|
|
300
|
-
process_file = self._process_dep_file_grpc if self.use_grpc else self._process_dep_file_rest
|
|
301
308
|
# Process the dependency files in parallel
|
|
302
309
|
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT_REQUESTS) as executor:
|
|
303
|
-
future_to_file = {
|
|
310
|
+
future_to_file = {
|
|
311
|
+
executor.submit(self._process_dep_file, file, depth, self.use_grpc): file for file in files_json
|
|
312
|
+
}
|
|
304
313
|
for future in concurrent.futures.as_completed(future_to_file):
|
|
305
314
|
response = future.result()
|
|
306
315
|
if response:
|
|
@@ -318,161 +327,104 @@ class ScanossGrpc(ScanossBase):
|
|
|
318
327
|
merged_response['status'] = response['status']
|
|
319
328
|
return merged_response
|
|
320
329
|
|
|
321
|
-
def
|
|
330
|
+
def _process_dep_file(self, file, depth: int = 1, use_grpc: Optional[bool] = None) -> Optional[dict]:
|
|
322
331
|
"""
|
|
323
|
-
Process a single file using gRPC
|
|
332
|
+
Process a single dependency file using either gRPC or REST
|
|
324
333
|
|
|
325
|
-
:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
request_id = str(uuid.uuid4())
|
|
330
|
-
try:
|
|
331
|
-
file_request = {'files': [file]}
|
|
332
|
-
request = ParseDict(file_request, DependencyRequest())
|
|
333
|
-
request.depth = depth
|
|
334
|
-
metadata = self.metadata[:]
|
|
335
|
-
metadata.append(('x-request-id', request_id))
|
|
336
|
-
self.print_debug(f'Sending dependency data via gRPC for decoration (rqId: {request_id})...')
|
|
337
|
-
resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout)
|
|
338
|
-
return MessageToDict(resp, preserving_proto_field_name=True)
|
|
339
|
-
except Exception as e:
|
|
340
|
-
self.print_stderr(
|
|
341
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
342
|
-
)
|
|
343
|
-
return None
|
|
334
|
+
Args:
|
|
335
|
+
file: dependency file purls
|
|
336
|
+
depth: depth to search (default: 1)
|
|
337
|
+
use_grpc: Whether to use gRPC or REST (None = use instance default)
|
|
344
338
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
Client function to call the rpc for Vulnerability GetVulnerabilities
|
|
348
|
-
It will either use REST (default) or gRPC depending on the use_grpc flag
|
|
349
|
-
:param purls: Message to send to the service
|
|
350
|
-
:return: Server response or None
|
|
339
|
+
Returns:
|
|
340
|
+
response JSON or None
|
|
351
341
|
"""
|
|
352
|
-
|
|
353
|
-
return self._get_vulnerabilities_grpc(purls)
|
|
354
|
-
else:
|
|
355
|
-
return self._get_vulnerabilities_rest(purls)
|
|
342
|
+
file_request = {'files': [file], 'depth': depth}
|
|
356
343
|
|
|
357
|
-
|
|
344
|
+
return self._call_api(
|
|
345
|
+
'dependencies.GetDependencies',
|
|
346
|
+
self.dependencies_stub.GetDependencies,
|
|
347
|
+
file_request,
|
|
348
|
+
DependencyRequest,
|
|
349
|
+
'Sending dependency data for decoration (rqId: {rqId})...',
|
|
350
|
+
use_grpc=use_grpc,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def get_vulnerabilities_json(self, purls: Optional[dict] = None, use_grpc: Optional[bool] = None) -> Optional[dict]:
|
|
358
354
|
"""
|
|
359
355
|
Client function to call the rpc for Vulnerability GetVulnerabilities
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
377
|
-
)
|
|
378
|
-
else:
|
|
379
|
-
if resp:
|
|
380
|
-
if not self._check_status_response(resp.status, request_id):
|
|
381
|
-
return None
|
|
382
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
383
|
-
del resp_dict['status']
|
|
384
|
-
return resp_dict
|
|
385
|
-
return None
|
|
356
|
+
It will either use REST (default) or gRPC
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
purls (dict): Message to send to the service
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Server response or None
|
|
363
|
+
"""
|
|
364
|
+
return self._call_api(
|
|
365
|
+
'vulnerabilities.GetComponentsVulnerabilities',
|
|
366
|
+
self.vuln_stub.GetComponentsVulnerabilities,
|
|
367
|
+
purls,
|
|
368
|
+
ComponentsRequest,
|
|
369
|
+
'Sending vulnerability data for decoration (rqId: {rqId})...',
|
|
370
|
+
use_grpc=use_grpc,
|
|
371
|
+
)
|
|
386
372
|
|
|
387
|
-
def get_semgrep_json(self, purls: dict) -> dict:
|
|
373
|
+
def get_semgrep_json(self, purls: Optional[dict] = None, use_grpc: Optional[bool] = None) -> Optional[dict]:
|
|
388
374
|
"""
|
|
389
375
|
Client function to call the rpc for Semgrep GetIssues
|
|
390
|
-
:param purls: Message to send to the service
|
|
391
|
-
:return: Server response or None
|
|
392
|
-
"""
|
|
393
|
-
if not purls:
|
|
394
|
-
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
395
|
-
return None
|
|
396
|
-
request_id = str(uuid.uuid4())
|
|
397
|
-
resp: SemgrepResponse
|
|
398
|
-
try:
|
|
399
|
-
request = ParseDict(purls, PurlRequest()) # Parse the JSON/Dict into the purl request object
|
|
400
|
-
metadata = self.metadata[:]
|
|
401
|
-
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
402
|
-
self.print_debug(f'Sending semgrep data for decoration (rqId: {request_id})...')
|
|
403
|
-
resp = self.semgrep_stub.GetIssues(request, metadata=metadata, timeout=self.timeout)
|
|
404
|
-
except Exception as e:
|
|
405
|
-
self.print_stderr(
|
|
406
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
407
|
-
)
|
|
408
|
-
else:
|
|
409
|
-
if resp:
|
|
410
|
-
if not self._check_status_response(resp.status, request_id):
|
|
411
|
-
return None
|
|
412
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
413
|
-
del resp_dict['status']
|
|
414
|
-
return resp_dict
|
|
415
|
-
return None
|
|
416
376
|
|
|
417
|
-
|
|
377
|
+
Args:
|
|
378
|
+
purls (dict): Message to send to the service
|
|
379
|
+
use_grpc (bool): Whether to use gRPC or REST
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Server response or None
|
|
383
|
+
"""
|
|
384
|
+
return self._call_api(
|
|
385
|
+
'semgrep.GetComponentsIssues',
|
|
386
|
+
self.semgrep_stub.GetComponentsIssues,
|
|
387
|
+
purls,
|
|
388
|
+
ComponentsRequest,
|
|
389
|
+
'Sending semgrep data for decoration (rqId: {rqId})...',
|
|
390
|
+
use_grpc=use_grpc,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def search_components_json(self, search: dict, use_grpc: Optional[bool] = None) -> Optional[dict]:
|
|
418
394
|
"""
|
|
419
395
|
Client function to call the rpc for Components SearchComponents
|
|
420
|
-
:param search: Message to send to the service
|
|
421
|
-
:return: Server response or None
|
|
422
|
-
"""
|
|
423
|
-
if not search:
|
|
424
|
-
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
425
|
-
return None
|
|
426
|
-
request_id = str(uuid.uuid4())
|
|
427
|
-
resp: CompSearchResponse
|
|
428
|
-
try:
|
|
429
|
-
request = ParseDict(search, CompSearchRequest()) # Parse the JSON/Dict into the purl request object
|
|
430
|
-
metadata = self.metadata[:]
|
|
431
|
-
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
432
|
-
self.print_debug(f'Sending component search data (rqId: {request_id})...')
|
|
433
|
-
resp = self.comp_search_stub.SearchComponents(request, metadata=metadata, timeout=self.timeout)
|
|
434
|
-
except Exception as e:
|
|
435
|
-
self.print_stderr(
|
|
436
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
437
|
-
)
|
|
438
|
-
else:
|
|
439
|
-
if resp:
|
|
440
|
-
if not self._check_status_response(resp.status, request_id):
|
|
441
|
-
return None
|
|
442
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
443
|
-
del resp_dict['status']
|
|
444
|
-
return resp_dict
|
|
445
|
-
return None
|
|
446
396
|
|
|
447
|
-
|
|
397
|
+
Args:
|
|
398
|
+
search (dict): Message to send to the service
|
|
399
|
+
Returns:
|
|
400
|
+
Server response or None
|
|
401
|
+
"""
|
|
402
|
+
return self._call_api(
|
|
403
|
+
'components.SearchComponents',
|
|
404
|
+
self.comp_search_stub.SearchComponents,
|
|
405
|
+
search,
|
|
406
|
+
CompSearchRequest,
|
|
407
|
+
'Sending component search data for decoration (rqId: {rqId})...',
|
|
408
|
+
use_grpc=use_grpc,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def get_component_versions_json(self, search: dict, use_grpc: Optional[bool] = None) -> Optional[dict]:
|
|
448
412
|
"""
|
|
449
413
|
Client function to call the rpc for Components GetComponentVersions
|
|
450
|
-
|
|
451
|
-
:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
except Exception as e:
|
|
465
|
-
self.print_stderr(
|
|
466
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
467
|
-
)
|
|
468
|
-
else:
|
|
469
|
-
if resp:
|
|
470
|
-
if not self._check_status_response(resp.status, request_id):
|
|
471
|
-
return None
|
|
472
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
473
|
-
del resp_dict['status']
|
|
474
|
-
return resp_dict
|
|
475
|
-
return None
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
search (dict): Message to send to the service
|
|
417
|
+
Returns:
|
|
418
|
+
Server response or None
|
|
419
|
+
"""
|
|
420
|
+
return self._call_api(
|
|
421
|
+
'components.GetComponentVersions',
|
|
422
|
+
self.comp_search_stub.GetComponentVersions,
|
|
423
|
+
search,
|
|
424
|
+
CompVersionRequest,
|
|
425
|
+
'Sending component version data for decoration (rqId: {rqId})...',
|
|
426
|
+
use_grpc=use_grpc,
|
|
427
|
+
)
|
|
476
428
|
|
|
477
429
|
def folder_hash_scan(self, request: Dict) -> Optional[Dict]:
|
|
478
430
|
"""
|
|
@@ -491,6 +443,44 @@ class ScanossGrpc(ScanossBase):
|
|
|
491
443
|
'Sending folder hash scan data (rqId: {rqId})...',
|
|
492
444
|
)
|
|
493
445
|
|
|
446
|
+
def _call_api(
|
|
447
|
+
self,
|
|
448
|
+
endpoint_key: str,
|
|
449
|
+
rpc_method,
|
|
450
|
+
request_input,
|
|
451
|
+
request_type,
|
|
452
|
+
debug_msg: Optional[str] = None,
|
|
453
|
+
use_grpc: Optional[bool] = None,
|
|
454
|
+
) -> Optional[Dict]:
|
|
455
|
+
"""
|
|
456
|
+
Unified method to call either gRPC or REST API based on configuration
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
endpoint_key (str): The key to lookup the REST endpoint in REST_ENDPOINTS
|
|
460
|
+
rpc_method: The gRPC stub method (used only if use_grpc is True)
|
|
461
|
+
request_input: Either a dict or a gRPC request object
|
|
462
|
+
request_type: The type of the gRPC request object (used only if use_grpc is True)
|
|
463
|
+
debug_msg (str, optional): Debug message template that can include {rqId} placeholder
|
|
464
|
+
use_grpc (bool, optional): Override the instance's use_grpc setting. If None, uses self.use_grpc
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
dict: The parsed response as a dictionary, or None if something went wrong
|
|
468
|
+
"""
|
|
469
|
+
if not request_input:
|
|
470
|
+
self.print_stderr('ERROR: No message supplied to send to service.')
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
# Determine whether to use gRPC or REST
|
|
474
|
+
use_grpc_flag = use_grpc if use_grpc is not None else self.use_grpc
|
|
475
|
+
|
|
476
|
+
if use_grpc_flag:
|
|
477
|
+
return self._call_rpc(rpc_method, request_input, request_type, debug_msg)
|
|
478
|
+
else:
|
|
479
|
+
# For REST, we only need the dict input
|
|
480
|
+
if not isinstance(request_input, dict):
|
|
481
|
+
request_input = MessageToDict(request_input, preserving_proto_field_name=True)
|
|
482
|
+
return self._call_rest(endpoint_key, request_input, debug_msg)
|
|
483
|
+
|
|
494
484
|
def _call_rpc(self, rpc_method, request_input, request_type, debug_msg: Optional[str] = None) -> Optional[Dict]:
|
|
495
485
|
"""
|
|
496
486
|
Call a gRPC method and return the response as a dictionary
|
|
@@ -510,20 +500,21 @@ class ScanossGrpc(ScanossBase):
|
|
|
510
500
|
else:
|
|
511
501
|
request_obj = request_input
|
|
512
502
|
metadata = self.metadata[:] + [('x-request-id', request_id)]
|
|
513
|
-
|
|
503
|
+
if debug_msg:
|
|
504
|
+
self.print_debug(debug_msg.format(rqId=request_id))
|
|
514
505
|
try:
|
|
515
506
|
resp = rpc_method(request_obj, metadata=metadata, timeout=self.timeout)
|
|
516
507
|
except grpc.RpcError as e:
|
|
517
508
|
raise ScanossGrpcError(
|
|
518
509
|
f'{e.__class__.__name__} while sending gRPC message (rqId: {request_id}): {e.details()}'
|
|
519
510
|
)
|
|
520
|
-
if resp and not self.
|
|
511
|
+
if resp and not self._check_status_response_grpc(resp.status, request_id):
|
|
521
512
|
return None
|
|
522
513
|
|
|
523
514
|
resp_dict = MessageToDict(resp, preserving_proto_field_name=True)
|
|
524
515
|
return resp_dict
|
|
525
516
|
|
|
526
|
-
def
|
|
517
|
+
def _check_status_response_grpc(self, status_response: StatusResponse, request_id: str = None) -> bool:
|
|
527
518
|
"""
|
|
528
519
|
Check the response object to see if the command was successful or not
|
|
529
520
|
:param status_response: Status Response
|
|
@@ -538,15 +529,59 @@ class ScanossGrpc(ScanossBase):
|
|
|
538
529
|
if status_code > ScanossGrpcStatusCode.SUCCESS:
|
|
539
530
|
ret_val = False # default to failed
|
|
540
531
|
msg = 'Unsuccessful'
|
|
541
|
-
if status_code == ScanossGrpcStatusCode.
|
|
532
|
+
if status_code == ScanossGrpcStatusCode.SUCCEEDED_WITH_WARNINGS:
|
|
542
533
|
msg = 'Succeeded with warnings'
|
|
543
534
|
ret_val = True # No need to fail as it succeeded with warnings
|
|
544
|
-
elif status_code == ScanossGrpcStatusCode.
|
|
535
|
+
elif status_code == ScanossGrpcStatusCode.WARNING:
|
|
545
536
|
msg = 'Failed with warnings'
|
|
546
537
|
self.print_stderr(f'{msg} (rqId: {request_id} - status: {status_code}): {status_response.message}')
|
|
547
538
|
return ret_val
|
|
548
539
|
return True
|
|
549
540
|
|
|
541
|
+
def check_status_response_rest(self, status_dict: dict, request_id: Optional[str] = None) -> bool:
|
|
542
|
+
"""
|
|
543
|
+
Check the REST response dictionary to see if the command was successful or not
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
status_dict (dict): Status dictionary from REST response containing 'status' and 'message' keys
|
|
547
|
+
request_id (str, optional): Request ID for logging
|
|
548
|
+
Returns:
|
|
549
|
+
bool: True if successful, False otherwise
|
|
550
|
+
"""
|
|
551
|
+
if not status_dict:
|
|
552
|
+
self.print_stderr(f'Warning: No status response supplied (rqId: {request_id}). Assuming it was ok.')
|
|
553
|
+
return True
|
|
554
|
+
|
|
555
|
+
if request_id:
|
|
556
|
+
self.print_debug(f'Checking response status (rqId: {request_id}): {status_dict}')
|
|
557
|
+
|
|
558
|
+
# Get status from dictionary - it can be either a string or nested dict
|
|
559
|
+
status = status_dict.get('status')
|
|
560
|
+
message = status_dict.get('message', '')
|
|
561
|
+
ret_val = True
|
|
562
|
+
|
|
563
|
+
# Handle case where status might be a string directly
|
|
564
|
+
if isinstance(status, str):
|
|
565
|
+
status_str = status.upper()
|
|
566
|
+
if status_str == ScanossRESTStatusCode.SUCCESS.value:
|
|
567
|
+
ret_val = True
|
|
568
|
+
elif status_str == ScanossRESTStatusCode.SUCCEEDED_WITH_WARNINGS.value:
|
|
569
|
+
self.print_stderr(f'Succeeded with warnings (rqId: {request_id}): {message}')
|
|
570
|
+
ret_val = True
|
|
571
|
+
elif status_str == ScanossRESTStatusCode.WARNING.value:
|
|
572
|
+
self.print_stderr(f'Failed with warnings (rqId: {request_id}): {message}')
|
|
573
|
+
ret_val = False
|
|
574
|
+
elif status_str == ScanossRESTStatusCode.FAILED.value:
|
|
575
|
+
self.print_stderr(f'Unsuccessful (rqId: {request_id}): {message}')
|
|
576
|
+
ret_val = False
|
|
577
|
+
else:
|
|
578
|
+
self.print_debug(f'Unknown status "{status_str}" (rqId: {request_id}). Assuming success.')
|
|
579
|
+
ret_val = True
|
|
580
|
+
|
|
581
|
+
# Otherwise asume success
|
|
582
|
+
self.print_debug(f'Unexpected status type {type(status)} (rqId: {request_id}). Assuming success.')
|
|
583
|
+
return ret_val
|
|
584
|
+
|
|
550
585
|
def _get_proxy_config(self):
|
|
551
586
|
"""
|
|
552
587
|
Set the grpc_proxy/http_proxy/https_proxy environment variables if PAC file has been specified
|
|
@@ -569,138 +604,165 @@ class ScanossGrpc(ScanossBase):
|
|
|
569
604
|
os.environ['http_proxy'] = proxies.get('http') or ''
|
|
570
605
|
os.environ['https_proxy'] = proxies.get('https') or ''
|
|
571
606
|
|
|
572
|
-
def get_provenance_json(self, purls: dict) ->
|
|
573
|
-
"""
|
|
574
|
-
Client function to call the rpc for GetComponentProvenance
|
|
575
|
-
:param purls: Message to send to the service
|
|
576
|
-
:return: Server response or None
|
|
607
|
+
def get_provenance_json(self, purls: dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
577
608
|
"""
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if not self._check_status_response(resp.status, request_id):
|
|
596
|
-
return None
|
|
597
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
598
|
-
return resp_dict
|
|
599
|
-
return None
|
|
609
|
+
Client function to call the rpc for GetComponentContributors
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
purls (dict): ComponentsRequest
|
|
613
|
+
use_grpc (bool): Whether to use gRPC or REST (None = use instance default)
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
dict: JSON response or None
|
|
617
|
+
"""
|
|
618
|
+
return self._call_api(
|
|
619
|
+
'geoprovenance.GetCountryContributorsByComponents',
|
|
620
|
+
self.provenance_stub.GetCountryContributorsByComponents,
|
|
621
|
+
purls,
|
|
622
|
+
ComponentsRequest,
|
|
623
|
+
'Sending data for provenance decoration (rqId: {rqId})...',
|
|
624
|
+
use_grpc=use_grpc,
|
|
625
|
+
)
|
|
600
626
|
|
|
601
|
-
def get_provenance_origin(self, request: Dict) -> Optional[Dict]:
|
|
627
|
+
def get_provenance_origin(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
602
628
|
"""
|
|
603
|
-
Client function to call the rpc for
|
|
629
|
+
Client function to call the rpc for GetOriginByComponents
|
|
604
630
|
|
|
605
631
|
Args:
|
|
606
|
-
request (Dict):
|
|
632
|
+
request (Dict): GetOriginByComponents Request
|
|
607
633
|
|
|
608
634
|
Returns:
|
|
609
635
|
Optional[Dict]: OriginResponse, or None if the request was not successfull
|
|
610
636
|
"""
|
|
611
|
-
return self.
|
|
612
|
-
|
|
637
|
+
return self._call_api(
|
|
638
|
+
'geoprovenance.GetOriginByComponents',
|
|
639
|
+
self.provenance_stub.GetOriginByComponents,
|
|
613
640
|
request,
|
|
614
|
-
|
|
641
|
+
ComponentsRequest,
|
|
615
642
|
'Sending data for provenance origin decoration (rqId: {rqId})...',
|
|
643
|
+
use_grpc=use_grpc,
|
|
616
644
|
)
|
|
617
645
|
|
|
618
|
-
def get_crypto_algorithms_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
646
|
+
def get_crypto_algorithms_for_purl(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
619
647
|
"""
|
|
620
|
-
Client function to call the rpc for
|
|
648
|
+
Client function to call the rpc for GetComponentsAlgorithms for a list of purls
|
|
621
649
|
|
|
622
650
|
Args:
|
|
623
|
-
request (Dict):
|
|
651
|
+
request (Dict): ComponentsRequest
|
|
652
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST (None = use instance default)
|
|
624
653
|
|
|
625
654
|
Returns:
|
|
626
655
|
Optional[Dict]: AlgorithmResponse, or None if the request was not successfull
|
|
627
656
|
"""
|
|
628
|
-
return self.
|
|
629
|
-
|
|
657
|
+
return self._call_api(
|
|
658
|
+
'cryptography.GetComponentsAlgorithms',
|
|
659
|
+
self.crypto_stub.GetComponentsAlgorithms,
|
|
630
660
|
request,
|
|
631
|
-
|
|
661
|
+
ComponentsRequest,
|
|
632
662
|
'Sending data for cryptographic algorithms decoration (rqId: {rqId})...',
|
|
663
|
+
use_grpc=use_grpc,
|
|
633
664
|
)
|
|
634
665
|
|
|
635
|
-
def get_crypto_algorithms_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
666
|
+
def get_crypto_algorithms_in_range_for_purl(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
636
667
|
"""
|
|
637
|
-
Client function to call the rpc for
|
|
668
|
+
Client function to call the rpc for GetComponentsAlgorithmsInRange for a list of purls
|
|
638
669
|
|
|
639
670
|
Args:
|
|
640
|
-
request (Dict):
|
|
671
|
+
request (Dict): ComponentsRequest
|
|
672
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST (None = use instance default)
|
|
641
673
|
|
|
642
674
|
Returns:
|
|
643
675
|
Optional[Dict]: AlgorithmsInRangeResponse, or None if the request was not successfull
|
|
644
676
|
"""
|
|
645
|
-
return self.
|
|
646
|
-
|
|
677
|
+
return self._call_api(
|
|
678
|
+
'cryptography.GetComponentsAlgorithmsInRange',
|
|
679
|
+
self.crypto_stub.GetComponentsAlgorithmsInRange,
|
|
647
680
|
request,
|
|
648
|
-
|
|
681
|
+
ComponentsRequest,
|
|
649
682
|
'Sending data for cryptographic algorithms in range decoration (rqId: {rqId})...',
|
|
683
|
+
use_grpc=use_grpc,
|
|
650
684
|
)
|
|
651
685
|
|
|
652
|
-
def get_encryption_hints_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
686
|
+
def get_encryption_hints_for_purl(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
653
687
|
"""
|
|
654
|
-
Client function to call the rpc for
|
|
688
|
+
Client function to call the rpc for GetComponentsEncryptionHints for a list of purls
|
|
655
689
|
|
|
656
690
|
Args:
|
|
657
|
-
request (Dict):
|
|
691
|
+
request (Dict): ComponentsRequest
|
|
692
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST (None = use instance default)
|
|
658
693
|
|
|
659
694
|
Returns:
|
|
660
695
|
Optional[Dict]: HintsResponse, or None if the request was not successfull
|
|
661
696
|
"""
|
|
662
|
-
return self.
|
|
663
|
-
|
|
697
|
+
return self._call_api(
|
|
698
|
+
'cryptography.GetComponentsEncryptionHints',
|
|
699
|
+
self.crypto_stub.GetComponentsEncryptionHints,
|
|
664
700
|
request,
|
|
665
|
-
|
|
701
|
+
ComponentsRequest,
|
|
666
702
|
'Sending data for encryption hints decoration (rqId: {rqId})...',
|
|
703
|
+
use_grpc=use_grpc,
|
|
667
704
|
)
|
|
668
705
|
|
|
669
|
-
def get_encryption_hints_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
706
|
+
def get_encryption_hints_in_range_for_purl(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
670
707
|
"""
|
|
671
|
-
Client function to call the rpc for
|
|
708
|
+
Client function to call the rpc for GetComponentsHintsInRange for a list of purls
|
|
672
709
|
|
|
673
710
|
Args:
|
|
674
|
-
request (Dict):
|
|
711
|
+
request (Dict): ComponentsRequest
|
|
712
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST (None = use instance default)
|
|
675
713
|
|
|
676
714
|
Returns:
|
|
677
715
|
Optional[Dict]: HintsInRangeResponse, or None if the request was not successfull
|
|
678
716
|
"""
|
|
679
|
-
return self.
|
|
680
|
-
|
|
717
|
+
return self._call_api(
|
|
718
|
+
'cryptography.GetComponentsHintsInRange',
|
|
719
|
+
self.crypto_stub.GetComponentsHintsInRange,
|
|
681
720
|
request,
|
|
682
|
-
|
|
721
|
+
ComponentsRequest,
|
|
683
722
|
'Sending data for encryption hints in range decoration (rqId: {rqId})...',
|
|
723
|
+
use_grpc=use_grpc,
|
|
684
724
|
)
|
|
685
725
|
|
|
686
|
-
def get_versions_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
726
|
+
def get_versions_in_range_for_purl(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
687
727
|
"""
|
|
688
|
-
Client function to call the rpc for
|
|
728
|
+
Client function to call the rpc for GetComponentsVersionsInRange for a list of purls
|
|
689
729
|
|
|
690
730
|
Args:
|
|
691
|
-
request (Dict):
|
|
731
|
+
request (Dict): ComponentsRequest
|
|
732
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST (None = use instance default)
|
|
692
733
|
|
|
693
734
|
Returns:
|
|
694
735
|
Optional[Dict]: VersionsInRangeResponse, or None if the request was not successfull
|
|
695
736
|
"""
|
|
696
|
-
return self.
|
|
697
|
-
|
|
737
|
+
return self._call_api(
|
|
738
|
+
'cryptography.GetComponentsVersionsInRange',
|
|
739
|
+
self.crypto_stub.GetComponentsVersionsInRange,
|
|
698
740
|
request,
|
|
699
|
-
|
|
741
|
+
ComponentsRequest,
|
|
700
742
|
'Sending data for cryptographic versions in range decoration (rqId: {rqId})...',
|
|
743
|
+
use_grpc=use_grpc,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
def get_licenses(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
747
|
+
"""
|
|
748
|
+
Client function to call the rpc for Licenses GetComponentsLicenses
|
|
749
|
+
It will either use REST (default) or gRPC depending on the use_grpc flag
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
request (Dict): ComponentsRequest
|
|
753
|
+
Returns:
|
|
754
|
+
Optional[Dict]: ComponentsLicenseResponse, or None if the request was not successfull
|
|
755
|
+
"""
|
|
756
|
+
return self._call_api(
|
|
757
|
+
'licenses.GetComponentsLicenses',
|
|
758
|
+
self.license_stub.GetComponentsLicenses,
|
|
759
|
+
request,
|
|
760
|
+
ComponentsRequest,
|
|
761
|
+
'Sending data for license decoration (rqId: {rqId})...',
|
|
762
|
+
use_grpc=use_grpc,
|
|
701
763
|
)
|
|
702
764
|
|
|
703
|
-
def load_generic_headers(self, url):
|
|
765
|
+
def load_generic_headers(self, url: Optional[str] = None):
|
|
704
766
|
"""
|
|
705
767
|
Adds custom headers from req_headers to metadata.
|
|
706
768
|
|
|
@@ -722,32 +784,91 @@ class ScanossGrpc(ScanossBase):
|
|
|
722
784
|
# Start of REST Client Functions
|
|
723
785
|
#
|
|
724
786
|
|
|
725
|
-
def
|
|
787
|
+
def _rest_get(self, uri: str, request_id: str, params: Optional[dict] = None) -> Optional[dict]:
|
|
788
|
+
"""
|
|
789
|
+
Send a GET request to the specified URI with optional query parameters.
|
|
790
|
+
|
|
791
|
+
Args:
|
|
792
|
+
uri (str): URI to send GET request to
|
|
793
|
+
request_id (str): request id
|
|
794
|
+
params (dict, optional): Optional query parameters as dictionary
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
dict: JSON response or None
|
|
798
|
+
"""
|
|
799
|
+
if not uri:
|
|
800
|
+
self.print_stderr('Error: Missing URI. Cannot perform GET request.')
|
|
801
|
+
return None
|
|
802
|
+
self.print_trace(f'Sending REST GET request to {uri}...')
|
|
803
|
+
headers = self.headers.copy()
|
|
804
|
+
headers['x-request-id'] = request_id
|
|
805
|
+
retry = 0
|
|
806
|
+
while retry <= self.retry_limit:
|
|
807
|
+
retry += 1
|
|
808
|
+
try:
|
|
809
|
+
response = self.session.get(uri, headers=headers, params=params, timeout=self.timeout)
|
|
810
|
+
response.raise_for_status() # Raises an HTTPError for bad responses
|
|
811
|
+
return response.json()
|
|
812
|
+
except (requests.exceptions.SSLError, requests.exceptions.ProxyError) as e:
|
|
813
|
+
self.print_stderr(f'ERROR: Exception ({e.__class__.__name__}) sending GET request - {e}.')
|
|
814
|
+
raise Exception(f'ERROR: The SCANOSS API GET request failed for {uri}') from e
|
|
815
|
+
except requests.exceptions.HTTPError as e:
|
|
816
|
+
self.print_stderr(f'ERROR: HTTP error sending GET request ({request_id}): {e}')
|
|
817
|
+
raise Exception(
|
|
818
|
+
f'ERROR: The SCANOSS API GET request failed with status {e.response.status_code} for {uri}'
|
|
819
|
+
) from e
|
|
820
|
+
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
|
821
|
+
if retry > self.retry_limit: # Timed out retry_limit or more times, fail
|
|
822
|
+
self.print_stderr(f'ERROR: {e.__class__.__name__} sending GET request ({request_id}): {e}')
|
|
823
|
+
raise Exception(
|
|
824
|
+
f'ERROR: The SCANOSS API GET request timed out ({e.__class__.__name__}) for {uri}'
|
|
825
|
+
) from e
|
|
826
|
+
else:
|
|
827
|
+
self.print_stderr(f'Warning: {e.__class__.__name__} communicating with {self.url}. Retrying...')
|
|
828
|
+
time.sleep(5)
|
|
829
|
+
except requests.exceptions.RequestException as e:
|
|
830
|
+
self.print_stderr(f'Error: Problem sending GET request to {uri}: {e}')
|
|
831
|
+
raise Exception(f'ERROR: The SCANOSS API GET request failed for {uri}') from e
|
|
832
|
+
except Exception as e:
|
|
833
|
+
self.print_stderr(
|
|
834
|
+
f'ERROR: Exception ({e.__class__.__name__}) sending GET request ({request_id}) to {uri}: {e}'
|
|
835
|
+
)
|
|
836
|
+
raise Exception(f'ERROR: The SCANOSS API GET request failed for {uri}') from e
|
|
837
|
+
return None
|
|
838
|
+
|
|
839
|
+
def _rest_post(self, uri: str, request_id: str, data: dict) -> Optional[dict]:
|
|
726
840
|
"""
|
|
727
841
|
Post the specified data to the given URI.
|
|
728
|
-
|
|
729
|
-
:
|
|
730
|
-
|
|
731
|
-
|
|
842
|
+
|
|
843
|
+
Args:
|
|
844
|
+
uri (str): URI to post to
|
|
845
|
+
request_id (str): request id
|
|
846
|
+
data (dict): json data to post
|
|
847
|
+
|
|
848
|
+
Returns:
|
|
849
|
+
dict: JSON response or None
|
|
732
850
|
"""
|
|
733
851
|
if not uri:
|
|
734
852
|
self.print_stderr('Error: Missing URI. Cannot search for project.')
|
|
735
853
|
return None
|
|
736
854
|
self.print_trace(f'Sending REST POST data to {uri}...')
|
|
737
|
-
headers = self.headers
|
|
738
|
-
headers['x-request-id'] = request_id
|
|
739
|
-
retry = 0
|
|
855
|
+
headers = self.headers.copy()
|
|
856
|
+
headers['x-request-id'] = request_id
|
|
857
|
+
retry = 0
|
|
740
858
|
while retry <= self.retry_limit:
|
|
741
859
|
retry += 1
|
|
742
860
|
try:
|
|
743
861
|
response = self.session.post(uri, headers=headers, json=data, timeout=self.timeout)
|
|
744
862
|
response.raise_for_status() # Raises an HTTPError for bad responses
|
|
745
863
|
return response.json()
|
|
746
|
-
except requests.exceptions.RequestException as e:
|
|
747
|
-
self.print_stderr(f'Error: Problem posting data to {uri}: {e}')
|
|
748
864
|
except (requests.exceptions.SSLError, requests.exceptions.ProxyError) as e:
|
|
749
865
|
self.print_stderr(f'ERROR: Exception ({e.__class__.__name__}) POSTing data - {e}.')
|
|
750
866
|
raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e
|
|
867
|
+
except requests.exceptions.HTTPError as e:
|
|
868
|
+
self.print_stderr(f'ERROR: HTTP error POSTing data ({request_id}): {e}')
|
|
869
|
+
raise Exception(
|
|
870
|
+
f'ERROR: The SCANOSS Decoration API request failed with status {e.response.status_code} for {uri}'
|
|
871
|
+
) from e
|
|
751
872
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
|
752
873
|
if retry > self.retry_limit: # Timed out retry_limit or more times, fail
|
|
753
874
|
self.print_stderr(f'ERROR: {e.__class__.__name__} POSTing decoration data ({request_id}): {e}')
|
|
@@ -757,6 +878,9 @@ class ScanossGrpc(ScanossBase):
|
|
|
757
878
|
else:
|
|
758
879
|
self.print_stderr(f'Warning: {e.__class__.__name__} communicating with {self.url}. Retrying...')
|
|
759
880
|
time.sleep(5)
|
|
881
|
+
except requests.exceptions.RequestException as e:
|
|
882
|
+
self.print_stderr(f'Error: Problem posting data to {uri}: {e}')
|
|
883
|
+
raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e
|
|
760
884
|
except Exception as e:
|
|
761
885
|
self.print_stderr(
|
|
762
886
|
f'ERROR: Exception ({e.__class__.__name__}) POSTing data ({request_id}) to {uri}: {e}'
|
|
@@ -764,52 +888,46 @@ class ScanossGrpc(ScanossBase):
|
|
|
764
888
|
raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e
|
|
765
889
|
return None
|
|
766
890
|
|
|
767
|
-
def
|
|
891
|
+
def _call_rest(self, endpoint_key: str, request_input: dict, debug_msg: Optional[str] = None) -> Optional[Dict]:
|
|
768
892
|
"""
|
|
769
|
-
|
|
770
|
-
:param purls: Purl Request dictionary
|
|
771
|
-
:return: Vulnerability Response, or None if the request was unsuccessful
|
|
772
|
-
"""
|
|
773
|
-
if not purls:
|
|
774
|
-
self.print_stderr('ERROR: No message supplied to send to REST decoration service.')
|
|
775
|
-
return None
|
|
776
|
-
request_id = str(uuid.uuid4())
|
|
777
|
-
self.print_debug(f'Sending data for Vulnerabilities via REST (request id: {request_id})...')
|
|
778
|
-
response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/vulnerabilities/components', request_id, purls)
|
|
779
|
-
self.print_trace(f'Received response for Vulnerabilities via REST (request id: {request_id}): {response}')
|
|
780
|
-
if response:
|
|
781
|
-
# Parse the JSON/Dict into the purl response
|
|
782
|
-
resp_obj = ParseDict(response, ComponentsVulnerabilityResponse(), True)
|
|
783
|
-
if resp_obj:
|
|
784
|
-
self.print_debug(f'Vulnerability Response: {resp_obj}')
|
|
785
|
-
if not self._check_status_response(resp_obj.status, request_id):
|
|
786
|
-
return None
|
|
787
|
-
del response['status']
|
|
788
|
-
return response
|
|
789
|
-
return None
|
|
893
|
+
Call a REST endpoint and return the response as a dictionary
|
|
790
894
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
895
|
+
Args:
|
|
896
|
+
endpoint_key (str): The key to lookup the REST endpoint in REST_ENDPOINTS
|
|
897
|
+
request_input (dict): The request data to send
|
|
898
|
+
debug_msg (str, optional): Debug message template that can include {rqId} placeholder.
|
|
794
899
|
|
|
795
|
-
:
|
|
796
|
-
|
|
797
|
-
:return: response JSON or None
|
|
900
|
+
Returns:
|
|
901
|
+
dict: The parsed REST response as a dictionary, or None if something went wrong
|
|
798
902
|
"""
|
|
903
|
+
if endpoint_key not in REST_ENDPOINTS:
|
|
904
|
+
raise ScanossGrpcError(f'Unknown REST endpoint key: {endpoint_key}')
|
|
905
|
+
|
|
906
|
+
endpoint_config = REST_ENDPOINTS[endpoint_key]
|
|
907
|
+
endpoint_path = endpoint_config['path']
|
|
908
|
+
method = endpoint_config['method']
|
|
909
|
+
endpoint_url = f'{self.orig_url}{DEFAULT_URI_PREFIX}{endpoint_path}'
|
|
799
910
|
request_id = str(uuid.uuid4())
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
911
|
+
|
|
912
|
+
if debug_msg:
|
|
913
|
+
self.print_debug(debug_msg.format(rqId=request_id))
|
|
914
|
+
|
|
915
|
+
if method == 'GET':
|
|
916
|
+
response = self._rest_get(endpoint_url, request_id, params=request_input)
|
|
917
|
+
else: # POST
|
|
918
|
+
response = self._rest_post(endpoint_url, request_id, request_input)
|
|
919
|
+
|
|
920
|
+
if response and 'status' in response and not self.check_status_response_rest(response['status'], request_id):
|
|
921
|
+
return None
|
|
922
|
+
|
|
923
|
+
return response
|
|
924
|
+
|
|
925
|
+
|
|
809
926
|
#
|
|
810
927
|
# End of ScanossGrpc Class
|
|
811
928
|
#
|
|
812
929
|
|
|
930
|
+
|
|
813
931
|
@dataclass
|
|
814
932
|
class GrpcConfig:
|
|
815
933
|
url: str = DEFAULT_URL
|
|
@@ -840,6 +958,7 @@ def create_grpc_config_from_args(args) -> GrpcConfig:
|
|
|
840
958
|
grpc_proxy=getattr(args, 'grpc_proxy', None),
|
|
841
959
|
)
|
|
842
960
|
|
|
961
|
+
|
|
843
962
|
#
|
|
844
963
|
# End of GrpcConfig class
|
|
845
|
-
#
|
|
964
|
+
#
|