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.
Files changed (36) hide show
  1. scanoss/__init__.py +1 -1
  2. scanoss/api/common/v2/scanoss_common_pb2.py +47 -22
  3. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +20 -0
  4. scanoss/api/components/v2/scanoss_components_pb2.py +54 -43
  5. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +77 -16
  6. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +58 -47
  7. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +105 -24
  8. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +48 -37
  9. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +63 -12
  10. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +42 -31
  11. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +63 -12
  12. scanoss/api/licenses/__init__.py +23 -0
  13. scanoss/api/licenses/v2/__init__.py +23 -0
  14. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  15. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  16. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +30 -19
  17. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
  18. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +34 -23
  19. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +49 -8
  20. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  21. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  22. scanoss/cli.py +8 -3
  23. scanoss/components.py +27 -8
  24. scanoss/data/build_date.txt +1 -1
  25. scanoss/inspection/dependency_track/project_violation.py +9 -8
  26. scanoss/scanner.py +3 -0
  27. scanoss/scanossapi.py +22 -24
  28. scanoss/scanossgrpc.py +196 -64
  29. scanoss/services/dependency_track_service.py +1 -1
  30. scanoss/threadeddependencies.py +19 -18
  31. {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/METADATA +2 -1
  32. {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/RECORD +36 -32
  33. {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/WHEEL +0 -0
  34. {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/entry_points.txt +0 -0
  35. {scanoss-1.31.4.dist-info → scanoss-1.32.0.dist-info}/licenses/LICENSE +0 -0
  36. {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 = 180,
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 > 5 else 180
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
- self.headers['User-Agent'] = f'scanoss-py/{__version__}'
107
- self.headers['user-agent'] = f'scanoss-py/{__version__}'
108
- self.load_generic_headers()
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 == 503: # Service limits have most likely been reached
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 >= 400:
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 self.url and not os.environ.get('SCANOSS_SCAN_URL'):
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 VulnerabilityResponse
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
- self.metadata.append(('user-agent', f'scanoss-py/{__version__}'))
144
- self.load_generic_headers()
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 is False: # insecure connection
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 gRPC service.')
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
- SUCCESS_STATUS = 'SUCCESS'
303
-
304
- merged_response = {'files': [], 'status': {'status': SUCCESS_STATUS, 'message': 'Success'}}
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 the any of the responses was not successful
310
- if 'status' in response and response['status']['status'] != SUCCESS_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: VulnerabilityResponse
367
+ resp: ComponentsVulnerabilityResponse
325
368
  try:
326
- request = ParseDict(purls, PurlRequest()) # Parse the JSON/Dict into the purl request object
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.GetVulnerabilities(request, metadata=metadata, timeout=self.timeout)
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 self.url and not os.environ.get('SCANOSS_GRPC_URL'):
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
@@ -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
- from enum import Enum
29
- from typing import Dict, Optional, Set
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
- else: # Search the file/folder for dependency files to parse
198
- if not self.sc_deps.run_scan(what_to_scan=what_to_scan):
199
- self._errors = True
200
- else:
201
- deps = self.sc_deps.produce_from_file()
202
- if dep_scope is not None:
203
- self.print_debug(f'Filtering {dep_scope.name} dependencies')
204
- if dep_scope_include is not None:
205
- self.print_debug(f"Including dependencies with '{dep_scope_include.split(',')}' scopes")
206
- if dep_scope_exclude is not None:
207
- self.print_debug(f"Excluding dependencies with '{dep_scope_exclude.split(',')}' scopes")
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.31.4
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"