scanoss 1.31.4__py3-none-any.whl → 1.32.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.
- scanoss/__init__.py +1 -1
- scanoss/api/common/v2/scanoss_common_pb2.py +47 -22
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +20 -0
- scanoss/api/components/v2/scanoss_components_pb2.py +54 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +77 -16
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +58 -47
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +105 -24
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +48 -37
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +63 -12
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +42 -31
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +63 -12
- 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 +30 -19
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +34 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +49 -8
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +8 -3
- scanoss/components.py +27 -8
- scanoss/data/build_date.txt +1 -1
- scanoss/inspection/dependency_track/project_violation.py +9 -8
- scanoss/scanner.py +3 -0
- scanoss/scanossapi.py +22 -24
- scanoss/scanossgrpc.py +196 -64
- scanoss/services/dependency_track_service.py +1 -1
- scanoss/threadeddependencies.py +19 -18
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/METADATA +2 -1
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/RECORD +36 -32
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/WHEEL +0 -0
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/top_level.txt +0 -0
scanoss/scanossapi.py
CHANGED
|
@@ -22,23 +22,23 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
import http.client as http_client
|
|
25
26
|
import logging
|
|
26
27
|
import os
|
|
27
28
|
import sys
|
|
28
29
|
import time
|
|
30
|
+
import uuid
|
|
29
31
|
from json.decoder import JSONDecodeError
|
|
32
|
+
|
|
30
33
|
import requests
|
|
31
|
-
import uuid
|
|
32
|
-
import http.client as http_client
|
|
33
34
|
import urllib3
|
|
34
|
-
|
|
35
35
|
from pypac import PACSession
|
|
36
36
|
from pypac.parser import PACFile
|
|
37
37
|
from urllib3.exceptions import InsecureRequestWarning
|
|
38
38
|
|
|
39
|
-
from .scanossbase import ScanossBase
|
|
40
39
|
from . import __version__
|
|
41
|
-
|
|
40
|
+
from .constants import DEFAULT_TIMEOUT, MIN_TIMEOUT
|
|
41
|
+
from .scanossbase import ScanossBase
|
|
42
42
|
|
|
43
43
|
DEFAULT_URL = 'https://api.osskb.org/scan/direct' # default free service URL
|
|
44
44
|
DEFAULT_URL2 = 'https://api.scanoss.com/scan/direct' # default premium service URL
|
|
@@ -52,7 +52,7 @@ class ScanossApi(ScanossBase):
|
|
|
52
52
|
Currently support posting scan requests to the SCANOSS streaming API
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
-
def __init__( # noqa: PLR0913, PLR0915
|
|
55
|
+
def __init__( # noqa: PLR0912, PLR0913, PLR0915
|
|
56
56
|
self,
|
|
57
57
|
scan_format: str = None,
|
|
58
58
|
flags: str = None,
|
|
@@ -61,7 +61,7 @@ class ScanossApi(ScanossBase):
|
|
|
61
61
|
debug: bool = False,
|
|
62
62
|
trace: bool = False,
|
|
63
63
|
quiet: bool = False,
|
|
64
|
-
timeout: int =
|
|
64
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
65
65
|
ver_details: str = None,
|
|
66
66
|
ignore_cert_errors: bool = False,
|
|
67
67
|
proxy: str = None,
|
|
@@ -87,30 +87,28 @@ class ScanossApi(ScanossBase):
|
|
|
87
87
|
HTTPS_PROXY='http://<ip>:<port>'
|
|
88
88
|
"""
|
|
89
89
|
super().__init__(debug, trace, quiet)
|
|
90
|
-
self.url = url
|
|
91
|
-
self.api_key = api_key
|
|
92
90
|
self.sbom = None
|
|
93
91
|
self.scan_format = scan_format if scan_format else 'plain'
|
|
94
92
|
self.flags = flags
|
|
95
|
-
self.timeout = timeout if timeout >
|
|
93
|
+
self.timeout = timeout if timeout > MIN_TIMEOUT else DEFAULT_TIMEOUT
|
|
96
94
|
self.retry_limit = retry if retry >= 0 else 5
|
|
97
95
|
self.ignore_cert_errors = ignore_cert_errors
|
|
98
96
|
self.req_headers = req_headers if req_headers else {}
|
|
99
97
|
self.headers = {}
|
|
100
|
-
|
|
98
|
+
# Set the correct URL/API key combination
|
|
99
|
+
self.url = url if url else SCANOSS_SCAN_URL
|
|
100
|
+
self.api_key = api_key if api_key else SCANOSS_API_KEY
|
|
101
|
+
if self.api_key and not url and not os.environ.get('SCANOSS_SCAN_URL'):
|
|
102
|
+
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
101
103
|
if ver_details:
|
|
102
104
|
self.headers['x-scanoss-client'] = ver_details
|
|
103
105
|
if self.api_key:
|
|
104
106
|
self.headers['X-Session'] = self.api_key
|
|
105
107
|
self.headers['x-api-key'] = self.api_key
|
|
106
|
-
|
|
107
|
-
self.headers['
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
self.url = url if url else SCANOSS_SCAN_URL
|
|
111
|
-
self.api_key = api_key if api_key else SCANOSS_API_KEY
|
|
112
|
-
if self.api_key and not url and not os.environ.get('SCANOSS_SCAN_URL'):
|
|
113
|
-
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
108
|
+
user_agent = f'scanoss-py/{__version__}'
|
|
109
|
+
self.headers['User-Agent'] = user_agent
|
|
110
|
+
self.headers['user-agent'] = user_agent
|
|
111
|
+
self.load_generic_headers(url)
|
|
114
112
|
|
|
115
113
|
if self.trace:
|
|
116
114
|
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
@@ -133,7 +131,7 @@ class ScanossApi(ScanossBase):
|
|
|
133
131
|
if self.proxies:
|
|
134
132
|
self.session.proxies = self.proxies
|
|
135
133
|
|
|
136
|
-
def scan(self, wfp: str, context: str = None, scan_id: int = None):
|
|
134
|
+
def scan(self, wfp: str, context: str = None, scan_id: int = None): # noqa: PLR0912, PLR0915
|
|
137
135
|
"""
|
|
138
136
|
Scan the specified WFP and return the JSON object
|
|
139
137
|
:param wfp: WFP to scan
|
|
@@ -192,7 +190,7 @@ class ScanossApi(ScanossBase):
|
|
|
192
190
|
else:
|
|
193
191
|
self.print_stderr(f'Warning: No response received from {self.url}. Retrying...')
|
|
194
192
|
time.sleep(5)
|
|
195
|
-
elif r.status_code ==
|
|
193
|
+
elif r.status_code == requests.codes.service_unavailable: # Service limits most likely reached
|
|
196
194
|
self.print_stderr(
|
|
197
195
|
f'ERROR: SCANOSS API rejected the scan request ({request_id}) due to '
|
|
198
196
|
f'service limits being exceeded'
|
|
@@ -202,7 +200,7 @@ class ScanossApi(ScanossBase):
|
|
|
202
200
|
f'ERROR: {r.status_code} - The SCANOSS API request ({request_id}) rejected '
|
|
203
201
|
f'for {self.url} due to service limits being exceeded.'
|
|
204
202
|
)
|
|
205
|
-
elif r.status_code >=
|
|
203
|
+
elif r.status_code >= requests.codes.bad_request:
|
|
206
204
|
if retry > self.retry_limit: # No response retry_limit or more times, fail
|
|
207
205
|
self.save_bad_req_wfp(scan_files, request_id, scan_id)
|
|
208
206
|
raise Exception(
|
|
@@ -269,7 +267,7 @@ class ScanossApi(ScanossBase):
|
|
|
269
267
|
self.sbom = sbom
|
|
270
268
|
return self
|
|
271
269
|
|
|
272
|
-
def load_generic_headers(self):
|
|
270
|
+
def load_generic_headers(self, url):
|
|
273
271
|
"""
|
|
274
272
|
Adds custom headers from req_headers to the headers collection.
|
|
275
273
|
|
|
@@ -279,7 +277,7 @@ class ScanossApi(ScanossBase):
|
|
|
279
277
|
if self.req_headers: # Load generic headers
|
|
280
278
|
for key, value in self.req_headers.items():
|
|
281
279
|
if key == 'x-api-key': # Set premium URL if x-api-key header is set
|
|
282
|
-
if not
|
|
280
|
+
if not url and not os.environ.get('SCANOSS_SCAN_URL'):
|
|
283
281
|
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
284
282
|
self.api_key = value
|
|
285
283
|
self.headers[key] = value
|
scanoss/scanossgrpc.py
CHANGED
|
@@ -23,8 +23,12 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import concurrent.futures
|
|
26
|
+
import http.client as http_client
|
|
26
27
|
import json
|
|
28
|
+
import logging
|
|
27
29
|
import os
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
28
32
|
import uuid
|
|
29
33
|
from dataclasses import dataclass
|
|
30
34
|
from enum import IntEnum
|
|
@@ -32,15 +36,20 @@ from typing import Dict, Optional
|
|
|
32
36
|
from urllib.parse import urlparse
|
|
33
37
|
|
|
34
38
|
import grpc
|
|
39
|
+
import requests
|
|
40
|
+
import urllib3
|
|
35
41
|
from google.protobuf.json_format import MessageToDict, ParseDict
|
|
42
|
+
from pypac import PACSession
|
|
36
43
|
from pypac.parser import PACFile
|
|
37
44
|
from pypac.resolver import ProxyResolver
|
|
45
|
+
from urllib3.exceptions import InsecureRequestWarning
|
|
38
46
|
|
|
39
47
|
from scanoss.api.scanning.v2.scanoss_scanning_pb2_grpc import ScanningStub
|
|
40
48
|
from scanoss.constants import DEFAULT_TIMEOUT
|
|
41
49
|
|
|
42
50
|
from . import __version__
|
|
43
51
|
from .api.common.v2.scanoss_common_pb2 import (
|
|
52
|
+
ComponentsRequest,
|
|
44
53
|
EchoRequest,
|
|
45
54
|
EchoResponse,
|
|
46
55
|
PurlRequest,
|
|
@@ -62,7 +71,7 @@ from .api.geoprovenance.v2.scanoss_geoprovenance_pb2_grpc import GeoProvenanceSt
|
|
|
62
71
|
from .api.scanning.v2.scanoss_scanning_pb2 import HFHRequest
|
|
63
72
|
from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
|
|
64
73
|
from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub
|
|
65
|
-
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import
|
|
74
|
+
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import ComponentsVulnerabilityResponse
|
|
66
75
|
from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
|
|
67
76
|
from .scanossbase import ScanossBase
|
|
68
77
|
|
|
@@ -70,21 +79,20 @@ DEFAULT_URL = 'https://api.osskb.org' # default free service URL
|
|
|
70
79
|
DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
|
|
71
80
|
SCANOSS_GRPC_URL = os.environ.get('SCANOSS_GRPC_URL') if os.environ.get('SCANOSS_GRPC_URL') else DEFAULT_URL
|
|
72
81
|
SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_API_KEY') else ''
|
|
82
|
+
DEFAULT_URI_PREFIX = '/v2'
|
|
73
83
|
|
|
74
|
-
MAX_CONCURRENT_REQUESTS = 5
|
|
84
|
+
MAX_CONCURRENT_REQUESTS = 5 # Maximum number of concurrent requests to make
|
|
75
85
|
|
|
76
86
|
|
|
77
87
|
class ScanossGrpcError(Exception):
|
|
78
88
|
"""
|
|
79
89
|
Custom exception for SCANOSS gRPC errors
|
|
80
90
|
"""
|
|
81
|
-
|
|
82
91
|
pass
|
|
83
92
|
|
|
84
93
|
|
|
85
94
|
class ScanossGrpcStatusCode(IntEnum):
|
|
86
95
|
"""Status codes for SCANOSS gRPC responses"""
|
|
87
|
-
|
|
88
96
|
SUCCESS = 1
|
|
89
97
|
SUCCESS_WITH_WARNINGS = 2
|
|
90
98
|
FAILED_WITH_WARNINGS = 3
|
|
@@ -96,7 +104,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
96
104
|
Client for gRPC functionality
|
|
97
105
|
"""
|
|
98
106
|
|
|
99
|
-
def __init__( # noqa: PLR0913, PLR0915
|
|
107
|
+
def __init__( # noqa: PLR0912, PLR0913, PLR0915
|
|
100
108
|
self,
|
|
101
109
|
url: str = None,
|
|
102
110
|
debug: bool = False,
|
|
@@ -110,6 +118,8 @@ class ScanossGrpc(ScanossBase):
|
|
|
110
118
|
grpc_proxy: str = None,
|
|
111
119
|
pac: PACFile = None,
|
|
112
120
|
req_headers: dict = None,
|
|
121
|
+
ignore_cert_errors: bool = False,
|
|
122
|
+
use_grpc: bool = False,
|
|
113
123
|
):
|
|
114
124
|
"""
|
|
115
125
|
|
|
@@ -127,27 +137,55 @@ class ScanossGrpc(ScanossBase):
|
|
|
127
137
|
grpc_proxy='http://<ip>:<port>'
|
|
128
138
|
"""
|
|
129
139
|
super().__init__(debug, trace, quiet)
|
|
130
|
-
self.url = url
|
|
131
140
|
self.api_key = api_key if api_key else SCANOSS_API_KEY
|
|
132
141
|
self.timeout = timeout
|
|
133
142
|
self.proxy = proxy
|
|
134
143
|
self.grpc_proxy = grpc_proxy
|
|
135
144
|
self.pac = pac
|
|
136
|
-
self.req_headers = req_headers
|
|
137
145
|
self.metadata = []
|
|
146
|
+
self.ignore_cert_errors = ignore_cert_errors
|
|
147
|
+
self.use_grpc = use_grpc
|
|
148
|
+
self.req_headers = req_headers if req_headers else {}
|
|
149
|
+
self.headers = {}
|
|
150
|
+
self.retry_limit = 2 # default retry limit
|
|
138
151
|
|
|
139
152
|
if self.api_key:
|
|
140
153
|
self.metadata.append(('x-api-key', api_key)) # Set API key if we have one
|
|
154
|
+
self.headers['X-Session'] = self.api_key
|
|
155
|
+
self.headers['x-api-key'] = self.api_key
|
|
141
156
|
if ver_details:
|
|
142
157
|
self.metadata.append(('x-scanoss-client', ver_details))
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
158
|
+
self.headers['x-scanoss-client'] = ver_details
|
|
159
|
+
user_agent = f'scanoss-py/{__version__}'
|
|
160
|
+
self.metadata.append(('user-agent', user_agent))
|
|
161
|
+
self.headers['User-Agent'] = user_agent
|
|
162
|
+
self.headers['user-agent'] = user_agent
|
|
163
|
+
self.headers['Content-Type'] = 'application/json'
|
|
164
|
+
# Set the correct URL/API key combination
|
|
146
165
|
self.url = url if url else SCANOSS_GRPC_URL
|
|
147
166
|
if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
|
|
148
167
|
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
168
|
+
self.load_generic_headers(url)
|
|
149
169
|
self.url = self.url.lower()
|
|
150
|
-
self.orig_url = self.url # Used for proxy lookup
|
|
170
|
+
self.orig_url = self.url.strip().rstrip('/') # Used for proxy lookup
|
|
171
|
+
# REST setup
|
|
172
|
+
if self.trace:
|
|
173
|
+
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
174
|
+
http_client.HTTPConnection.debuglevel = 1
|
|
175
|
+
if pac and not proxy: # Set up a PAC session if requested (and no proxy has been explicitly set)
|
|
176
|
+
self.print_debug('Setting up PAC session...')
|
|
177
|
+
self.session = PACSession(pac=pac)
|
|
178
|
+
else:
|
|
179
|
+
self.session = requests.sessions.Session()
|
|
180
|
+
if self.ignore_cert_errors:
|
|
181
|
+
self.print_debug('Ignoring cert errors...')
|
|
182
|
+
urllib3.disable_warnings(InsecureRequestWarning)
|
|
183
|
+
self.session.verify = False
|
|
184
|
+
elif ca_cert:
|
|
185
|
+
self.session.verify = ca_cert
|
|
186
|
+
self.proxies = {'https': proxy, 'http': proxy} if proxy else None
|
|
187
|
+
if self.proxies:
|
|
188
|
+
self.session.proxies = self.proxies
|
|
151
189
|
|
|
152
190
|
secure = True if self.url.startswith('https:') else False # Is it a secure connection?
|
|
153
191
|
if self.url.startswith('http'):
|
|
@@ -162,7 +200,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
162
200
|
cert_data = ScanossGrpc._load_cert(ca_cert)
|
|
163
201
|
self.print_debug(f'Setting up (secure: {secure}) connection to {self.url}...')
|
|
164
202
|
self._get_proxy_config()
|
|
165
|
-
if secure
|
|
203
|
+
if not secure: # insecure connection
|
|
166
204
|
self.comp_search_stub = ComponentsStub(grpc.insecure_channel(self.url))
|
|
167
205
|
self.crypto_stub = CryptographyStub(grpc.insecure_channel(self.url))
|
|
168
206
|
self.dependencies_stub = DependenciesStub(grpc.insecure_channel(self.url))
|
|
@@ -206,17 +244,6 @@ class ScanossGrpc(ScanossBase):
|
|
|
206
244
|
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
207
245
|
)
|
|
208
246
|
else:
|
|
209
|
-
# self.print_stderr(f'resp: {resp} - call: {call}')
|
|
210
|
-
# response_id = ""
|
|
211
|
-
# if not call:
|
|
212
|
-
# self.print_stderr(f'No call to leverage.')
|
|
213
|
-
# for key, value in call.trailing_metadata():
|
|
214
|
-
# print('Greeter client received trailing metadata: key=%s value=%s' % (key, value))
|
|
215
|
-
#
|
|
216
|
-
# for key, value in call.trailing_metadata():
|
|
217
|
-
# if key == 'x-response-id':
|
|
218
|
-
# response_id = value
|
|
219
|
-
# self.print_stderr(f'Response ID: {response_id}. Metadata: {call.trailing_metadata()}')
|
|
220
247
|
if resp:
|
|
221
248
|
return resp.message
|
|
222
249
|
self.print_stderr(f'ERROR: Problem sending Echo request ({message}) to {self.url}. rqId: {request_id}')
|
|
@@ -264,54 +291,70 @@ class ScanossGrpc(ScanossBase):
|
|
|
264
291
|
if not dependencies:
|
|
265
292
|
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
266
293
|
return None
|
|
267
|
-
|
|
268
294
|
files_json = dependencies.get('files')
|
|
269
|
-
|
|
270
295
|
if files_json is None or len(files_json) == 0:
|
|
271
|
-
self.print_stderr('ERROR: No dependency data supplied to send to
|
|
296
|
+
self.print_stderr('ERROR: No dependency data supplied to send to decoration service.')
|
|
272
297
|
return None
|
|
273
|
-
|
|
274
|
-
def process_file(file):
|
|
275
|
-
request_id = str(uuid.uuid4())
|
|
276
|
-
try:
|
|
277
|
-
file_request = {'files': [file]}
|
|
278
|
-
|
|
279
|
-
request = ParseDict(file_request, DependencyRequest())
|
|
280
|
-
request.depth = depth
|
|
281
|
-
metadata = self.metadata[:]
|
|
282
|
-
metadata.append(('x-request-id', request_id))
|
|
283
|
-
self.print_debug(f'Sending dependency data for decoration (rqId: {request_id})...')
|
|
284
|
-
resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout)
|
|
285
|
-
|
|
286
|
-
return MessageToDict(resp, preserving_proto_field_name=True)
|
|
287
|
-
except Exception as e:
|
|
288
|
-
self.print_stderr(
|
|
289
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
290
|
-
)
|
|
291
|
-
return None
|
|
292
|
-
|
|
293
298
|
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
|
+
# Process the dependency files in parallel
|
|
294
302
|
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT_REQUESTS) as executor:
|
|
295
|
-
future_to_file = {executor.submit(process_file, file): file for file in files_json}
|
|
296
|
-
|
|
303
|
+
future_to_file = {executor.submit(process_file, file, depth): file for file in files_json}
|
|
297
304
|
for future in concurrent.futures.as_completed(future_to_file):
|
|
298
305
|
response = future.result()
|
|
299
306
|
if response:
|
|
300
307
|
all_responses.append(response)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
308
|
+
# End of concurrent processing
|
|
309
|
+
success_status = 'SUCCESS'
|
|
310
|
+
merged_response = {'files': [], 'status': {'status': success_status, 'message': 'Success'}}
|
|
311
|
+
# Merge the responses
|
|
305
312
|
for response in all_responses:
|
|
306
313
|
if response:
|
|
307
314
|
if 'files' in response and len(response['files']) > 0:
|
|
308
315
|
merged_response['files'].append(response['files'][0])
|
|
309
|
-
# Overwrite the status if
|
|
310
|
-
if 'status' in response and response['status']['status'] !=
|
|
316
|
+
# Overwrite the status if any of the responses was not successful
|
|
317
|
+
if 'status' in response and response['status']['status'] != success_status:
|
|
311
318
|
merged_response['status'] = response['status']
|
|
312
319
|
return merged_response
|
|
313
320
|
|
|
321
|
+
def _process_dep_file_grpc(self, file, depth: int = 1) -> dict:
|
|
322
|
+
"""
|
|
323
|
+
Process a single file using gRPC
|
|
324
|
+
|
|
325
|
+
:param file: dependency file purls
|
|
326
|
+
:param depth: depth to search (default: 1)
|
|
327
|
+
:return: response JSON or None
|
|
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
|
|
344
|
+
|
|
314
345
|
def get_vulnerabilities_json(self, purls: dict) -> dict:
|
|
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
|
|
351
|
+
"""
|
|
352
|
+
if self.use_grpc:
|
|
353
|
+
return self._get_vulnerabilities_grpc(purls)
|
|
354
|
+
else:
|
|
355
|
+
return self._get_vulnerabilities_rest(purls)
|
|
356
|
+
|
|
357
|
+
def _get_vulnerabilities_grpc(self, purls: dict) -> dict:
|
|
315
358
|
"""
|
|
316
359
|
Client function to call the rpc for Vulnerability GetVulnerabilities
|
|
317
360
|
:param purls: Message to send to the service
|
|
@@ -321,13 +364,13 @@ class ScanossGrpc(ScanossBase):
|
|
|
321
364
|
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
322
365
|
return None
|
|
323
366
|
request_id = str(uuid.uuid4())
|
|
324
|
-
resp:
|
|
367
|
+
resp: ComponentsVulnerabilityResponse
|
|
325
368
|
try:
|
|
326
|
-
request = ParseDict(purls,
|
|
369
|
+
request = ParseDict(purls, ComponentsRequest()) # Parse the JSON/Dict into the purl request object
|
|
327
370
|
metadata = self.metadata[:]
|
|
328
371
|
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
329
372
|
self.print_debug(f'Sending vulnerability data for decoration (rqId: {request_id})...')
|
|
330
|
-
resp = self.vuln_stub.
|
|
373
|
+
resp = self.vuln_stub.GetComponentsVulnerabilities(request, metadata=metadata, timeout=self.timeout)
|
|
331
374
|
except Exception as e:
|
|
332
375
|
self.print_stderr(
|
|
333
376
|
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
@@ -462,14 +505,11 @@ class ScanossGrpc(ScanossBase):
|
|
|
462
505
|
dict: The parsed gRPC response as a dictionary, or None if something went wrong
|
|
463
506
|
"""
|
|
464
507
|
request_id = str(uuid.uuid4())
|
|
465
|
-
|
|
466
508
|
if isinstance(request_input, dict):
|
|
467
509
|
request_obj = ParseDict(request_input, request_type())
|
|
468
510
|
else:
|
|
469
511
|
request_obj = request_input
|
|
470
|
-
|
|
471
512
|
metadata = self.metadata[:] + [('x-request-id', request_id)]
|
|
472
|
-
|
|
473
513
|
self.print_debug(debug_msg.format(rqId=request_id))
|
|
474
514
|
try:
|
|
475
515
|
resp = rpc_method(request_obj, metadata=metadata, timeout=self.timeout)
|
|
@@ -477,7 +517,6 @@ class ScanossGrpc(ScanossBase):
|
|
|
477
517
|
raise ScanossGrpcError(
|
|
478
518
|
f'{e.__class__.__name__} while sending gRPC message (rqId: {request_id}): {e.details()}'
|
|
479
519
|
)
|
|
480
|
-
|
|
481
520
|
if resp and not self._check_status_response(resp.status, request_id):
|
|
482
521
|
return None
|
|
483
522
|
|
|
@@ -661,7 +700,7 @@ class ScanossGrpc(ScanossBase):
|
|
|
661
700
|
'Sending data for cryptographic versions in range decoration (rqId: {rqId})...',
|
|
662
701
|
)
|
|
663
702
|
|
|
664
|
-
def load_generic_headers(self):
|
|
703
|
+
def load_generic_headers(self, url):
|
|
665
704
|
"""
|
|
666
705
|
Adds custom headers from req_headers to metadata.
|
|
667
706
|
|
|
@@ -671,17 +710,106 @@ class ScanossGrpc(ScanossBase):
|
|
|
671
710
|
if self.req_headers: # Load generic headers
|
|
672
711
|
for key, value in self.req_headers.items():
|
|
673
712
|
if key == 'x-api-key': # Set premium URL if x-api-key header is set
|
|
674
|
-
if not
|
|
713
|
+
if not url and not os.environ.get('SCANOSS_GRPC_URL'):
|
|
675
714
|
self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
|
|
676
715
|
self.api_key = value
|
|
677
716
|
self.metadata.append((key, value))
|
|
717
|
+
self.headers[key] = value
|
|
718
|
+
|
|
719
|
+
#
|
|
720
|
+
# End of gRPC Client Functions
|
|
721
|
+
#
|
|
722
|
+
# Start of REST Client Functions
|
|
723
|
+
#
|
|
678
724
|
|
|
725
|
+
def rest_post(self, uri: str, request_id: str, data: dict) -> dict:
|
|
726
|
+
"""
|
|
727
|
+
Post the specified data to the given URI.
|
|
728
|
+
:param request_id: request id
|
|
729
|
+
:param uri: URI to post to
|
|
730
|
+
:param data: json data to post
|
|
731
|
+
:return: JSON response or None
|
|
732
|
+
"""
|
|
733
|
+
if not uri:
|
|
734
|
+
self.print_stderr('Error: Missing URI. Cannot search for project.')
|
|
735
|
+
return None
|
|
736
|
+
self.print_trace(f'Sending REST POST data to {uri}...')
|
|
737
|
+
headers = self.headers
|
|
738
|
+
headers['x-request-id'] = request_id # send a unique request id for each post
|
|
739
|
+
retry = 0 # Add some retry logic to cater for timeouts, etc.
|
|
740
|
+
while retry <= self.retry_limit:
|
|
741
|
+
retry += 1
|
|
742
|
+
try:
|
|
743
|
+
response = self.session.post(uri, headers=headers, json=data, timeout=self.timeout)
|
|
744
|
+
response.raise_for_status() # Raises an HTTPError for bad responses
|
|
745
|
+
return response.json()
|
|
746
|
+
except requests.exceptions.RequestException as e:
|
|
747
|
+
self.print_stderr(f'Error: Problem posting data to {uri}: {e}')
|
|
748
|
+
except (requests.exceptions.SSLError, requests.exceptions.ProxyError) as e:
|
|
749
|
+
self.print_stderr(f'ERROR: Exception ({e.__class__.__name__}) POSTing data - {e}.')
|
|
750
|
+
raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e
|
|
751
|
+
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
|
752
|
+
if retry > self.retry_limit: # Timed out retry_limit or more times, fail
|
|
753
|
+
self.print_stderr(f'ERROR: {e.__class__.__name__} POSTing decoration data ({request_id}): {e}')
|
|
754
|
+
raise Exception(
|
|
755
|
+
f'ERROR: The SCANOSS Decoration API request timed out ({e.__class__.__name__}) for {uri}'
|
|
756
|
+
) from e
|
|
757
|
+
else:
|
|
758
|
+
self.print_stderr(f'Warning: {e.__class__.__name__} communicating with {self.url}. Retrying...')
|
|
759
|
+
time.sleep(5)
|
|
760
|
+
except Exception as e:
|
|
761
|
+
self.print_stderr(
|
|
762
|
+
f'ERROR: Exception ({e.__class__.__name__}) POSTing data ({request_id}) to {uri}: {e}'
|
|
763
|
+
)
|
|
764
|
+
raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e
|
|
765
|
+
return None
|
|
679
766
|
|
|
767
|
+
def _get_vulnerabilities_rest(self, purls: dict):
|
|
768
|
+
"""
|
|
769
|
+
Get the vulnerabilities for the given purls using REST API
|
|
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
|
|
790
|
+
|
|
791
|
+
def _process_dep_file_rest(self, file, depth: int = 1) -> dict:
|
|
792
|
+
"""
|
|
793
|
+
Porcess a single dependency file using REST
|
|
794
|
+
|
|
795
|
+
:param file: dependency file purls
|
|
796
|
+
:param depth: depth to search (default: 1)
|
|
797
|
+
:return: response JSON or None
|
|
798
|
+
"""
|
|
799
|
+
request_id = str(uuid.uuid4())
|
|
800
|
+
self.print_debug(f'Sending data for Dependencies via REST (request id: {request_id})...')
|
|
801
|
+
file_request = {'files': [file], 'depth': depth}
|
|
802
|
+
response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/dependencies/dependencies',
|
|
803
|
+
request_id, file_request
|
|
804
|
+
)
|
|
805
|
+
self.print_trace(f'Received response for Dependencies via REST (request id: {request_id}): {response}')
|
|
806
|
+
if response:
|
|
807
|
+
return response
|
|
808
|
+
return None
|
|
680
809
|
#
|
|
681
810
|
# End of ScanossGrpc Class
|
|
682
811
|
#
|
|
683
812
|
|
|
684
|
-
|
|
685
813
|
@dataclass
|
|
686
814
|
class GrpcConfig:
|
|
687
815
|
url: str = DEFAULT_URL
|
|
@@ -711,3 +839,7 @@ def create_grpc_config_from_args(args) -> GrpcConfig:
|
|
|
711
839
|
proxy=getattr(args, 'proxy', None),
|
|
712
840
|
grpc_proxy=getattr(args, 'grpc_proxy', None),
|
|
713
841
|
)
|
|
842
|
+
|
|
843
|
+
#
|
|
844
|
+
# End of GrpcConfig class
|
|
845
|
+
#
|
|
@@ -41,7 +41,7 @@ class DependencyTrackService(ScanossBase):
|
|
|
41
41
|
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
42
42
|
if not url:
|
|
43
43
|
raise ValueError("Error: Dependency Track URL is required")
|
|
44
|
-
self.url = url.rstrip('/')
|
|
44
|
+
self.url = url.strip().rstrip('/')
|
|
45
45
|
if not api_key:
|
|
46
46
|
raise ValueError("Error: Dependency Track API key is required")
|
|
47
47
|
self.api_key = api_key
|
scanoss/threadeddependencies.py
CHANGED
|
@@ -22,12 +22,12 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import threading
|
|
26
|
-
import queue
|
|
27
25
|
import json
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
import queue
|
|
27
|
+
import threading
|
|
30
28
|
from dataclasses import dataclass
|
|
29
|
+
from enum import Enum
|
|
30
|
+
from typing import Dict
|
|
31
31
|
|
|
32
32
|
from .scancodedeps import ScancodeDeps
|
|
33
33
|
from .scanossbase import ScanossBase
|
|
@@ -63,7 +63,7 @@ class ThreadedDependencies(ScanossBase):
|
|
|
63
63
|
inputs: queue.Queue = queue.Queue()
|
|
64
64
|
output: queue.Queue = queue.Queue()
|
|
65
65
|
|
|
66
|
-
def __init__(
|
|
66
|
+
def __init__( # noqa: PLR0913
|
|
67
67
|
self,
|
|
68
68
|
sc_deps: ScancodeDeps,
|
|
69
69
|
grpc_api: ScanossGrpc,
|
|
@@ -180,13 +180,15 @@ class ThreadedDependencies(ScanossBase):
|
|
|
180
180
|
return self.filter_dependencies(
|
|
181
181
|
deps, lambda purl: (exclude and purl not in exclude) or (not exclude and purl in include)
|
|
182
182
|
)
|
|
183
|
+
return None
|
|
183
184
|
|
|
184
|
-
def scan_dependencies(
|
|
185
|
+
def scan_dependencies( # noqa: PLR0912
|
|
185
186
|
self, dep_scope: SCOPE = None, dep_scope_include: str = None, dep_scope_exclude: str = None
|
|
186
187
|
) -> None:
|
|
187
188
|
"""
|
|
188
189
|
Scan for dependencies from the given file/dir or from an input file (from the input queue).
|
|
189
190
|
"""
|
|
191
|
+
# TODO refactor to simplify branches based on PLR0912
|
|
190
192
|
current_thread = threading.get_ident()
|
|
191
193
|
self.print_trace(f'Starting dependency worker {current_thread}...')
|
|
192
194
|
try:
|
|
@@ -194,18 +196,17 @@ class ThreadedDependencies(ScanossBase):
|
|
|
194
196
|
deps = None
|
|
195
197
|
if what_to_scan.startswith(DEP_FILE_PREFIX): # We have a pre-parsed dependency file, load it
|
|
196
198
|
deps = self.sc_deps.load_from_file(what_to_scan.strip(DEP_FILE_PREFIX))
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
deps = self.filter_dependencies_by_scopes(deps, dep_scope, dep_scope_include, dep_scope_exclude)
|
|
199
|
+
elif not self.sc_deps.run_scan(what_to_scan=what_to_scan):
|
|
200
|
+
self._errors = True
|
|
201
|
+
else:
|
|
202
|
+
deps = self.sc_deps.produce_from_file()
|
|
203
|
+
if dep_scope is not None:
|
|
204
|
+
self.print_debug(f'Filtering {dep_scope.name} dependencies')
|
|
205
|
+
if dep_scope_include is not None:
|
|
206
|
+
self.print_debug(f"Including dependencies with '{dep_scope_include.split(',')}' scopes")
|
|
207
|
+
if dep_scope_exclude is not None:
|
|
208
|
+
self.print_debug(f"Excluding dependencies with '{dep_scope_exclude.split(',')}' scopes")
|
|
209
|
+
deps = self.filter_dependencies_by_scopes(deps, dep_scope, dep_scope_include, dep_scope_exclude)
|
|
209
210
|
|
|
210
211
|
if not self._errors:
|
|
211
212
|
if deps is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scanoss
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.32.0
|
|
4
4
|
Summary: Simple Python library to leverage the SCANOSS APIs
|
|
5
5
|
Home-page: https://scanoss.com
|
|
6
6
|
Author: SCANOSS
|
|
@@ -30,6 +30,7 @@ Requires-Dist: packageurl-python
|
|
|
30
30
|
Requires-Dist: pathspec
|
|
31
31
|
Requires-Dist: jsonschema
|
|
32
32
|
Requires-Dist: crc
|
|
33
|
+
Requires-Dist: protoc-gen-openapiv2
|
|
33
34
|
Requires-Dist: cyclonedx-python-lib[validation]
|
|
34
35
|
Provides-Extra: fast-winnowing
|
|
35
36
|
Requires-Dist: scanoss_winnowing>=0.5.0; extra == "fast-winnowing"
|