scanoss 1.31.5__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.
Files changed (36) hide show
  1. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  2. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  3. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  4. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  5. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  6. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  7. scanoss/__init__.py +1 -1
  8. scanoss/api/common/v2/scanoss_common_pb2.py +8 -6
  9. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +5 -1
  10. scanoss/api/components/v2/scanoss_components_pb2.py +46 -32
  11. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +6 -6
  12. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +107 -29
  13. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +545 -9
  14. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +29 -21
  15. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +1 -0
  16. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +51 -19
  17. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +189 -1
  18. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +27 -27
  19. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +18 -18
  20. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +29 -13
  21. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +102 -8
  22. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +21 -21
  23. scanoss/cli.py +196 -144
  24. scanoss/components.py +80 -50
  25. scanoss/cryptography.py +64 -44
  26. scanoss/cyclonedx.py +22 -0
  27. scanoss/data/build_date.txt +1 -1
  28. scanoss/scanner.py +3 -0
  29. scanoss/scanossapi.py +22 -24
  30. scanoss/scanossgrpc.py +538 -287
  31. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/METADATA +4 -3
  32. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/RECORD +36 -34
  33. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/WHEEL +0 -0
  34. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/entry_points.txt +0 -0
  35. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/licenses/LICENSE +0 -0
  36. {scanoss-1.31.5.dist-info → scanoss-1.34.0.dist-info}/top_level.txt +0 -0
scanoss/components.py CHANGED
@@ -29,6 +29,9 @@ from typing import List, Optional, TextIO
29
29
 
30
30
  from pypac.parser import PACFile
31
31
 
32
+ from scanoss.cyclonedx import CycloneDx
33
+ from scanoss.utils.file import validate_json_file
34
+
32
35
  from .scanner import Scanner
33
36
  from .scanossbase import ScanossBase
34
37
  from .scanossgrpc import ScanossGrpc
@@ -52,6 +55,8 @@ class Components(ScanossBase):
52
55
  ca_cert: str = None,
53
56
  pac: PACFile = None,
54
57
  req_headers: dict = None,
58
+ ignore_cert_errors: bool = False,
59
+ use_grpc: bool = False,
55
60
  ):
56
61
  """
57
62
  Handle all component style requests
@@ -66,9 +71,13 @@ class Components(ScanossBase):
66
71
  :param grpc_proxy: Specific gRPC proxy (optional)
67
72
  :param ca_cert: TLS client certificate (optional)
68
73
  :param pac: Proxy Auto-Config file (optional)
74
+ :param req_headers: Additional headers to send with requests (optional)
75
+ :param ignore_cert_errors: Ignore TLS certificate errors (optional)
76
+ :param use_grpc: Use gRPC instead of HTTP (optional)
69
77
  """
70
78
  super().__init__(debug, trace, quiet)
71
79
  ver_details = Scanner.version_details()
80
+ self.use_grpc = use_grpc
72
81
  self.grpc_api = ScanossGrpc(
73
82
  url=grpc_url,
74
83
  debug=debug,
@@ -82,26 +91,41 @@ class Components(ScanossBase):
82
91
  grpc_proxy=grpc_proxy,
83
92
  timeout=timeout,
84
93
  req_headers=req_headers,
94
+ ignore_cert_errors=ignore_cert_errors,
85
95
  )
96
+ self.cdx = CycloneDx(debug=self.debug)
97
+
98
+ def load_comps(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
99
+ """
100
+ Load the specified components and return a dictionary
101
+
102
+ :param json_file: JSON Components file (optional)
103
+ :param purls: list pf PURLs (optional)
104
+ :return: Components Request dictionary or None
105
+ """
106
+ return self.load_purls(json_file, purls, 'components')
86
107
 
87
- def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
108
+ def load_purls(
109
+ self, json_file: Optional[str] = None, purls: Optional[List[str]] = None, field: str = 'purls'
110
+ ) -> Optional[dict]:
88
111
  """
89
112
  Load the specified purls and return a dictionary
90
113
 
91
114
  :param json_file: JSON PURL file (optional)
92
115
  :param purls: list of PURLs (optional)
116
+ :param field: Name of the dictionary field to store the purls in (default: 'purls')
93
117
  :return: PURL Request dictionary or None
94
118
  """
95
119
  if json_file:
96
- if not os.path.isfile(json_file) or not os.access(json_file, os.R_OK):
97
- self.print_stderr(f'ERROR: JSON file does not exist, is not a file, or is not readable: {json_file}')
120
+ result = validate_json_file(json_file)
121
+ if not result.is_valid:
122
+ self.print_stderr(f'ERROR: Problem parsing input JSON: {result.error}')
98
123
  return None
99
- with open(json_file, 'r') as f:
100
- try:
101
- purl_request = json.loads(f.read())
102
- except Exception as e:
103
- self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
104
- return None
124
+
125
+ if self.cdx.is_cyclonedx_json(json.dumps(result.data)):
126
+ purl_request = self.cdx.get_purls_request_from_cdx(result.data, field)
127
+ else:
128
+ purl_request = result.data
105
129
  elif purls:
106
130
  if not all(isinstance(purl, str) for purl in purls):
107
131
  self.print_stderr('ERROR: PURLs must be a list of strings.')
@@ -109,14 +133,14 @@ class Components(ScanossBase):
109
133
  parsed_purls = []
110
134
  for p in purls:
111
135
  parsed_purls.append({'purl': p})
112
- purl_request = {'purls': parsed_purls}
136
+ purl_request = {field: parsed_purls}
113
137
  else:
114
138
  self.print_stderr('ERROR: No purls specified to process.')
115
139
  return None
116
- purl_count = len(purl_request.get('purls', []))
117
- self.print_debug(f'Parsed Purls ({purl_count}): {purl_request}')
140
+ purl_count = len(purl_request.get(field, []))
141
+ self.print_debug(f'Parsed {field} ({purl_count}): {purl_request}')
118
142
  if purl_count == 0:
119
- self.print_stderr('ERROR: No PURLs parsed from request.')
143
+ self.print_stderr(f'ERROR: No {field} parsed from request.')
120
144
  return None
121
145
  return purl_request
122
146
 
@@ -142,8 +166,8 @@ class Components(ScanossBase):
142
166
  """
143
167
  Open the given filename if requested, otherwise return STDOUT
144
168
 
145
- :param filename:
146
- :return:
169
+ :param filename: filename to open or None to return STDOUT
170
+ :return: file descriptor or None
147
171
  """
148
172
  file = sys.stdout
149
173
  if filename:
@@ -166,32 +190,6 @@ class Components(ScanossBase):
166
190
  self.print_trace(f'Closing file: {filename}')
167
191
  file.close()
168
192
 
169
- def get_crypto_details(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
170
- """
171
- Retrieve the cryptographic details for the supplied PURLs
172
-
173
- :param json_file: PURL JSON request file (optional)
174
- :param purls: PURL request array (optional)
175
- :param output_file: output filename (optional). Default: STDOUT
176
- :return: True on success, False otherwise
177
- """
178
- success = False
179
- purls_request = self.load_purls(json_file, purls)
180
- if purls_request is None or len(purls_request) == 0:
181
- return False
182
- file = self._open_file_or_sdtout(output_file)
183
- if file is None:
184
- return False
185
- self.print_msg('Sending PURLs to Crypto API for decoration...')
186
- response = self.grpc_api.get_crypto_json(purls_request)
187
- if response:
188
- print(json.dumps(response, indent=2, sort_keys=True), file=file)
189
- success = True
190
- if output_file:
191
- self.print_msg(f'Results written to: {output_file}')
192
- self._close_file(output_file, file)
193
- return success
194
-
195
193
  def get_vulnerabilities(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
196
194
  """
197
195
  Retrieve any vulnerabilities related to the given PURLs
@@ -202,14 +200,14 @@ class Components(ScanossBase):
202
200
  :return: True on success, False otherwise
203
201
  """
204
202
  success = False
205
- purls_request = self.load_purls(json_file, purls)
203
+ purls_request = self.load_comps(json_file, purls)
206
204
  if purls_request is None or len(purls_request) == 0:
207
205
  return False
208
206
  file = self._open_file_or_sdtout(output_file)
209
207
  if file is None:
210
208
  return False
211
209
  self.print_msg('Sending PURLs to Vulnerability API for decoration...')
212
- response = self.grpc_api.get_vulnerabilities_json(purls_request)
210
+ response = self.grpc_api.get_vulnerabilities_json(purls_request, use_grpc=self.use_grpc)
213
211
  if response:
214
212
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
215
213
  success = True
@@ -228,14 +226,14 @@ class Components(ScanossBase):
228
226
  :return: True on success, False otherwise
229
227
  """
230
228
  success = False
231
- purls_request = self.load_purls(json_file, purls)
229
+ purls_request = self.load_comps(json_file, purls)
232
230
  if purls_request is None or len(purls_request) == 0:
233
231
  return False
234
232
  file = self._open_file_or_sdtout(output_file)
235
233
  if file is None:
236
234
  return False
237
235
  self.print_msg('Sending PURLs to Semgrep API for decoration...')
238
- response = self.grpc_api.get_semgrep_json(purls_request)
236
+ response = self.grpc_api.get_semgrep_json(purls_request, use_grpc=self.use_grpc)
239
237
  if response:
240
238
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
241
239
  success = True
@@ -285,7 +283,7 @@ class Components(ScanossBase):
285
283
  if file is None:
286
284
  return False
287
285
  self.print_msg('Sending search data to Components API...')
288
- response = self.grpc_api.search_components_json(request)
286
+ response = self.grpc_api.search_components_json(request, use_grpc=self.use_grpc)
289
287
  if response:
290
288
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
291
289
  success = True
@@ -321,7 +319,7 @@ class Components(ScanossBase):
321
319
  if file is None:
322
320
  return False
323
321
  self.print_msg('Sending PURLs to Component Versions API...')
324
- response = self.grpc_api.get_component_versions_json(request)
322
+ response = self.grpc_api.get_component_versions_json(request, use_grpc=self.use_grpc)
325
323
  if response:
326
324
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
327
325
  success = True
@@ -346,7 +344,7 @@ class Components(ScanossBase):
346
344
  bool: True on success, False otherwise
347
345
  """
348
346
  success = False
349
- purls_request = self.load_purls(json_file, purls)
347
+ purls_request = self.load_comps(json_file, purls)
350
348
  if purls_request is None or len(purls_request) == 0:
351
349
  return False
352
350
  file = self._open_file_or_sdtout(output_file)
@@ -354,10 +352,42 @@ class Components(ScanossBase):
354
352
  return False
355
353
  if origin:
356
354
  self.print_msg('Sending PURLs to Geo Provenance Origin API for decoration...')
357
- response = self.grpc_api.get_provenance_origin(purls_request)
355
+ response = self.grpc_api.get_provenance_origin(purls_request, use_grpc=self.use_grpc)
358
356
  else:
359
357
  self.print_msg('Sending PURLs to Geo Provenance Declared API for decoration...')
360
- response = self.grpc_api.get_provenance_json(purls_request)
358
+ response = self.grpc_api.get_provenance_json(purls_request, use_grpc=self.use_grpc)
359
+ if response:
360
+ print(json.dumps(response, indent=2, sort_keys=True), file=file)
361
+ success = True
362
+ if output_file:
363
+ self.print_msg(f'Results written to: {output_file}')
364
+ self._close_file(output_file, file)
365
+ return success
366
+
367
+ def get_licenses(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
368
+ """
369
+ Retrieve the license details for the supplied PURLs
370
+
371
+ Args:
372
+ json_file (str, optional): Input JSON file. Defaults to None.
373
+ purls (None, optional): PURLs to retrieve license details for. Defaults to None.
374
+ output_file (str, optional): Output file. Defaults to None.
375
+
376
+ Returns:
377
+ bool: True on success, False otherwise
378
+ """
379
+ success = False
380
+
381
+ purls_request = self.load_purls(json_file, purls)
382
+ if not purls_request:
383
+ return False
384
+ file = self._open_file_or_sdtout(output_file)
385
+ if file is None:
386
+ return False
387
+
388
+ # We'll use the new ComponentBatchRequest instead of deprecated PurlRequest for the license api
389
+ component_batch_request = {'components': purls_request.get('purls')}
390
+ response = self.grpc_api.get_licenses(component_batch_request, use_grpc=self.use_grpc)
361
391
  if response:
362
392
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
363
393
  success = True
scanoss/cryptography.py CHANGED
@@ -2,6 +2,7 @@ import json
2
2
  from dataclasses import dataclass
3
3
  from typing import Dict, List, Optional
4
4
 
5
+ from scanoss.cyclonedx import CycloneDx
5
6
  from scanoss.scanossbase import ScanossBase
6
7
  from scanoss.scanossgrpc import ScanossGrpc
7
8
  from scanoss.utils.abstract_presenter import AbstractPresenter
@@ -18,14 +19,47 @@ MIN_SPLIT_PARTS = 2
18
19
  @dataclass
19
20
  class CryptographyConfig:
20
21
  purl: List[str]
22
+ debug: bool = False
23
+ header: Optional[str] = None
21
24
  input_file: Optional[str] = None
22
25
  output_file: Optional[str] = None
23
- header: Optional[str] = None
24
- debug: bool = False
25
- trace: bool = False
26
26
  quiet: bool = False
27
+ trace: bool = False
28
+ use_grpc: bool = False
27
29
  with_range: bool = False
28
30
 
31
+ def _process_input_file(self) -> dict:
32
+ """
33
+ Process and validate the input file, returning the validated purl_request.
34
+
35
+ Returns:
36
+ dict: The validated purl_request dictionary
37
+
38
+ Raises:
39
+ ScanossCryptographyError: If the input file is invalid
40
+ """
41
+ result = validate_json_file(self.input_file)
42
+ if not result.is_valid:
43
+ raise ScanossCryptographyError(
44
+ f'There was a problem with the purl input file. {result.error}'
45
+ )
46
+
47
+ cdx = CycloneDx(debug=self.debug)
48
+ if cdx.is_cyclonedx_json(json.dumps(result.data)):
49
+ purl_request = cdx.get_purls_request_from_cdx(result.data)
50
+ else:
51
+ purl_request = result.data
52
+
53
+ if (
54
+ not isinstance(purl_request, dict)
55
+ or 'purls' not in purl_request
56
+ or not isinstance(purl_request['purls'], list)
57
+ or not all(isinstance(p, dict) and 'purl' in p for p in purl_request['purls'])
58
+ ):
59
+ raise ScanossCryptographyError('The supplied input file is not in the correct PurlRequest format.')
60
+
61
+ return purl_request
62
+
29
63
  def __post_init__(self):
30
64
  """
31
65
  Validate that the configuration is valid.
@@ -36,22 +70,11 @@ class CryptographyConfig:
36
70
  parts = purl.split('@')
37
71
  if not (len(parts) >= MIN_SPLIT_PARTS and parts[1]):
38
72
  raise ScanossCryptographyError(
39
- f'Invalid PURL format: "{purl}".' f'It must include a version (e.g., pkg:type/name@version)'
73
+ f'Invalid PURL format: "{purl}".It must include a version (e.g., pkg:type/name@version)'
40
74
  )
41
75
  if self.input_file:
42
- input_file_validation = validate_json_file(self.input_file)
43
- if not input_file_validation.is_valid:
44
- raise ScanossCryptographyError(
45
- f'There was a problem with the purl input file. {input_file_validation.error}'
46
- )
47
- if (
48
- not isinstance(input_file_validation.data, dict)
49
- or 'purls' not in input_file_validation.data
50
- or not isinstance(input_file_validation.data['purls'], list)
51
- or not all(isinstance(p, dict) and 'purl' in p for p in input_file_validation.data['purls'])
52
- ):
53
- raise ScanossCryptographyError('The supplied input file is not in the correct PurlRequest format.')
54
- purls = input_file_validation.data['purls']
76
+ purl_request = self._process_input_file()
77
+ purls = purl_request['purls']
55
78
  purls_with_requirement = []
56
79
  if self.with_range and any('requirement' not in p for p in purls):
57
80
  raise ScanossCryptographyError(
@@ -69,13 +92,14 @@ class CryptographyConfig:
69
92
  def create_cryptography_config_from_args(args) -> CryptographyConfig:
70
93
  return CryptographyConfig(
71
94
  debug=getattr(args, 'debug', False),
72
- trace=getattr(args, 'trace', False),
73
- quiet=getattr(args, 'quiet', False),
74
- with_range=getattr(args, 'with_range', False),
75
- purl=getattr(args, 'purl', []),
95
+ header=getattr(args, 'header', None),
76
96
  input_file=getattr(args, 'input', None),
77
97
  output_file=getattr(args, 'output', None),
78
- header=getattr(args, 'header', None),
98
+ purl=getattr(args, 'purl', []),
99
+ quiet=getattr(args, 'quiet', False),
100
+ trace=getattr(args, 'trace', False),
101
+ use_grpc=getattr(args, 'grpc', False),
102
+ with_range=getattr(args, 'with_range', False),
79
103
  )
80
104
 
81
105
 
@@ -112,7 +136,7 @@ class Cryptography:
112
136
 
113
137
  self.client = client
114
138
  self.config = config
115
- self.purls_request = self._build_purls_request()
139
+ self.components_request = self._build_components_request()
116
140
  self.results = None
117
141
 
118
142
  def get_algorithms(self) -> Optional[Dict]:
@@ -123,15 +147,16 @@ class Cryptography:
123
147
  Optional[Dict]: The folder hash response from the gRPC client, or None if an error occurs.
124
148
  """
125
149
 
126
- if not self.purls_request:
150
+ if not self.components_request or not self.components_request.get('components'):
127
151
  raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
128
- self.base.print_stderr(
129
- f'Getting cryptographic algorithms for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
130
- )
152
+ components_str = ', '.join(p['purl'] for p in self.components_request['components'])
153
+ self.base.print_stderr(f'Getting cryptographic algorithms for {components_str}')
131
154
  if self.config.with_range:
132
- response = self.client.get_crypto_algorithms_in_range_for_purl(self.purls_request)
155
+ response = self.client.get_crypto_algorithms_in_range_for_purl(
156
+ self.components_request, self.config.use_grpc
157
+ )
133
158
  else:
134
- response = self.client.get_crypto_algorithms_for_purl(self.purls_request)
159
+ response = self.client.get_crypto_algorithms_for_purl(self.components_request, self.config.use_grpc)
135
160
  if response:
136
161
  self.results = response
137
162
 
@@ -145,17 +170,17 @@ class Cryptography:
145
170
  Optional[Dict]: The encryption hints response from the gRPC client, or None if an error occurs.
146
171
  """
147
172
 
148
- if not self.purls_request:
173
+ if not self.components_request or not self.components_request.get('components'):
149
174
  raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
150
175
  self.base.print_stderr(
151
176
  f'Getting encryption hints '
152
177
  f'{"in range" if self.config.with_range else ""} '
153
- f'for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
178
+ f'for {", ".join([p["purl"] for p in self.components_request["components"]])}'
154
179
  )
155
180
  if self.config.with_range:
156
- response = self.client.get_encryption_hints_in_range_for_purl(self.purls_request)
181
+ response = self.client.get_encryption_hints_in_range_for_purl(self.components_request, self.config.use_grpc)
157
182
  else:
158
- response = self.client.get_encryption_hints_for_purl(self.purls_request)
183
+ response = self.client.get_encryption_hints_for_purl(self.components_request, self.config.use_grpc)
159
184
  if response:
160
185
  self.results = response
161
186
 
@@ -169,34 +194,29 @@ class Cryptography:
169
194
  Optional[Dict]: The versions in range response from the gRPC client, or None if an error occurs.
170
195
  """
171
196
 
172
- if not self.purls_request:
197
+ if not self.components_request or not self.components_request.get('components'):
173
198
  raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
174
199
 
175
- self.base.print_stderr(
176
- f'Getting versions in range for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
177
- )
200
+ components_str = ', '.join(p['purl'] for p in self.components_request['components'])
201
+ self.base.print_stderr(f'Getting versions in range for {components_str}')
178
202
 
179
- response = self.client.get_versions_in_range_for_purl(self.purls_request)
203
+ response = self.client.get_versions_in_range_for_purl(self.components_request, self.config.use_grpc)
180
204
  if response:
181
205
  self.results = response
182
206
 
183
207
  return self.results
184
208
 
185
- def _build_purls_request(
209
+ def _build_components_request(
186
210
  self,
187
211
  ) -> Optional[dict]:
188
212
  """
189
213
  Load the specified purls from a JSON file or a list of PURLs and return a dictionary
190
214
 
191
- Args:
192
- json_file (Optional[str], optional): The JSON file containing the PURLs. Defaults to None.
193
- purls (Optional[List[str]], optional): The list of PURLs. Defaults to None.
194
-
195
215
  Returns:
196
216
  Optional[dict]: The dictionary containing the PURLs
197
217
  """
198
218
  return {
199
- 'purls': [
219
+ 'components': [
200
220
  {
201
221
  'purl': self._remove_version_from_purl(purl),
202
222
  'requirement': self._extract_version_from_purl(purl),
scanoss/cyclonedx.py CHANGED
@@ -394,6 +394,7 @@ class CycloneDx(ScanossBase):
394
394
  def is_cyclonedx_json(self, json_string: str) -> bool:
395
395
  """
396
396
  Validate if the given JSON string is a valid CycloneDX JSON string
397
+
397
398
  Args:
398
399
  json_string (str): JSON string to validate
399
400
  Returns:
@@ -410,6 +411,27 @@ class CycloneDx(ScanossBase):
410
411
  self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
411
412
  return False
412
413
 
414
+ def get_purls_request_from_cdx(self, cdx_dict: dict, field: str = 'purls') -> dict:
415
+ """
416
+ Get the list of PURL requests (purl + requirement) from the given CDX dictionary
417
+
418
+ Args:
419
+ cdx_dict (dict): CDX dictionary to parse
420
+ field (str): Field to extract from the CDX dictionary
421
+ Returns:
422
+ list[dict]: List of PURL requests (purl + requirement)
423
+ """
424
+ components = cdx_dict.get('components', [])
425
+ parsed_purls = []
426
+ for component in components:
427
+ version = component.get('version')
428
+ if version:
429
+ parsed_purls.append({'purl': component.get('purl'), 'requirement': version})
430
+ else:
431
+ parsed_purls.append({'purl': component.get('purl')})
432
+ purl_request = {field: parsed_purls}
433
+ return purl_request
434
+
413
435
 
414
436
  #
415
437
  # End of CycloneDX Class
@@ -1 +1 @@
1
- date: 20250827093647, utime: 1756287407
1
+ date: 20251006080454, utime: 1759737894
scanoss/scanner.py CHANGED
@@ -107,6 +107,7 @@ class Scanner(ScanossBase):
107
107
  skip_md5_ids=None,
108
108
  scan_settings: 'ScanossSettings | None' = None,
109
109
  req_headers: dict = None,
110
+ use_grpc: bool = False,
110
111
  ):
111
112
  """
112
113
  Initialise scanning class, including Winnowing, ScanossApi, ThreadedScanning
@@ -173,6 +174,8 @@ class Scanner(ScanossBase):
173
174
  pac=pac,
174
175
  grpc_proxy=grpc_proxy,
175
176
  req_headers=self.req_headers,
177
+ ignore_cert_errors=ignore_cert_errors,
178
+ use_grpc=use_grpc
176
179
  )
177
180
  self.threaded_deps = ThreadedDependencies(sc_deps, grpc_api, debug=debug, quiet=quiet, trace=trace)
178
181
  self.nb_threads = nb_threads
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