scanoss 1.32.0__py3-none-any.whl → 1.34.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) 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 +193 -146
  24. scanoss/components.py +57 -46
  25. scanoss/cryptography.py +64 -44
  26. scanoss/cyclonedx.py +22 -0
  27. scanoss/data/build_date.txt +1 -1
  28. scanoss/scanossgrpc.py +433 -314
  29. {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/METADATA +4 -3
  30. {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/RECORD +34 -32
  31. {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/WHEEL +0 -0
  32. {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/entry_points.txt +0 -0
  33. {scanoss-1.32.0.dist-info → scanoss-1.34.0.dist-info}/licenses/LICENSE +0 -0
  34. {scanoss-1.32.0.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
@@ -74,6 +77,7 @@ class Components(ScanossBase):
74
77
  """
75
78
  super().__init__(debug, trace, quiet)
76
79
  ver_details = Scanner.version_details()
80
+ self.use_grpc = use_grpc
77
81
  self.grpc_api = ScanossGrpc(
78
82
  url=grpc_url,
79
83
  debug=debug,
@@ -88,10 +92,10 @@ class Components(ScanossBase):
88
92
  timeout=timeout,
89
93
  req_headers=req_headers,
90
94
  ignore_cert_errors=ignore_cert_errors,
91
- use_grpc=use_grpc,
92
95
  )
96
+ self.cdx = CycloneDx(debug=self.debug)
93
97
 
94
- def load_comps(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None)-> Optional[dict]:
98
+ def load_comps(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
95
99
  """
96
100
  Load the specified components and return a dictionary
97
101
 
@@ -101,8 +105,9 @@ class Components(ScanossBase):
101
105
  """
102
106
  return self.load_purls(json_file, purls, 'components')
103
107
 
104
- def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None, field:str = 'purls'
105
- ) -> Optional[dict]:
108
+ def load_purls(
109
+ self, json_file: Optional[str] = None, purls: Optional[List[str]] = None, field: str = 'purls'
110
+ ) -> Optional[dict]:
106
111
  """
107
112
  Load the specified purls and return a dictionary
108
113
 
@@ -112,15 +117,15 @@ class Components(ScanossBase):
112
117
  :return: PURL Request dictionary or None
113
118
  """
114
119
  if json_file:
115
- if not os.path.isfile(json_file) or not os.access(json_file, os.R_OK):
116
- 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}')
117
123
  return None
118
- with open(json_file, 'r') as f:
119
- try:
120
- purl_request = json.loads(f.read())
121
- except Exception as e:
122
- self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
123
- 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
124
129
  elif purls:
125
130
  if not all(isinstance(purl, str) for purl in purls):
126
131
  self.print_stderr('ERROR: PURLs must be a list of strings.')
@@ -185,32 +190,6 @@ class Components(ScanossBase):
185
190
  self.print_trace(f'Closing file: {filename}')
186
191
  file.close()
187
192
 
188
- def get_crypto_details(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
189
- """
190
- Retrieve the cryptographic details for the supplied PURLs
191
-
192
- :param json_file: PURL JSON request file (optional)
193
- :param purls: PURL request array (optional)
194
- :param output_file: output filename (optional). Default: STDOUT
195
- :return: True on success, False otherwise
196
- """
197
- success = False
198
- purls_request = self.load_purls(json_file, purls)
199
- if purls_request is None or len(purls_request) == 0:
200
- return False
201
- file = self._open_file_or_sdtout(output_file)
202
- if file is None:
203
- return False
204
- self.print_msg('Sending PURLs to Crypto API for decoration...')
205
- response = self.grpc_api.get_crypto_json(purls_request)
206
- if response:
207
- print(json.dumps(response, indent=2, sort_keys=True), file=file)
208
- success = True
209
- if output_file:
210
- self.print_msg(f'Results written to: {output_file}')
211
- self._close_file(output_file, file)
212
- return success
213
-
214
193
  def get_vulnerabilities(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
215
194
  """
216
195
  Retrieve any vulnerabilities related to the given PURLs
@@ -228,7 +207,7 @@ class Components(ScanossBase):
228
207
  if file is None:
229
208
  return False
230
209
  self.print_msg('Sending PURLs to Vulnerability API for decoration...')
231
- response = self.grpc_api.get_vulnerabilities_json(purls_request)
210
+ response = self.grpc_api.get_vulnerabilities_json(purls_request, use_grpc=self.use_grpc)
232
211
  if response:
233
212
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
234
213
  success = True
@@ -247,14 +226,14 @@ class Components(ScanossBase):
247
226
  :return: True on success, False otherwise
248
227
  """
249
228
  success = False
250
- purls_request = self.load_purls(json_file, purls)
229
+ purls_request = self.load_comps(json_file, purls)
251
230
  if purls_request is None or len(purls_request) == 0:
252
231
  return False
253
232
  file = self._open_file_or_sdtout(output_file)
254
233
  if file is None:
255
234
  return False
256
235
  self.print_msg('Sending PURLs to Semgrep API for decoration...')
257
- response = self.grpc_api.get_semgrep_json(purls_request)
236
+ response = self.grpc_api.get_semgrep_json(purls_request, use_grpc=self.use_grpc)
258
237
  if response:
259
238
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
260
239
  success = True
@@ -304,7 +283,7 @@ class Components(ScanossBase):
304
283
  if file is None:
305
284
  return False
306
285
  self.print_msg('Sending search data to Components API...')
307
- response = self.grpc_api.search_components_json(request)
286
+ response = self.grpc_api.search_components_json(request, use_grpc=self.use_grpc)
308
287
  if response:
309
288
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
310
289
  success = True
@@ -340,7 +319,7 @@ class Components(ScanossBase):
340
319
  if file is None:
341
320
  return False
342
321
  self.print_msg('Sending PURLs to Component Versions API...')
343
- response = self.grpc_api.get_component_versions_json(request)
322
+ response = self.grpc_api.get_component_versions_json(request, use_grpc=self.use_grpc)
344
323
  if response:
345
324
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
346
325
  success = True
@@ -365,7 +344,7 @@ class Components(ScanossBase):
365
344
  bool: True on success, False otherwise
366
345
  """
367
346
  success = False
368
- purls_request = self.load_purls(json_file, purls)
347
+ purls_request = self.load_comps(json_file, purls)
369
348
  if purls_request is None or len(purls_request) == 0:
370
349
  return False
371
350
  file = self._open_file_or_sdtout(output_file)
@@ -373,10 +352,42 @@ class Components(ScanossBase):
373
352
  return False
374
353
  if origin:
375
354
  self.print_msg('Sending PURLs to Geo Provenance Origin API for decoration...')
376
- response = self.grpc_api.get_provenance_origin(purls_request)
355
+ response = self.grpc_api.get_provenance_origin(purls_request, use_grpc=self.use_grpc)
377
356
  else:
378
357
  self.print_msg('Sending PURLs to Geo Provenance Declared API for decoration...')
379
- 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)
380
391
  if response:
381
392
  print(json.dumps(response, indent=2, sort_keys=True), file=file)
382
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: 20250901122016, utime: 1756729216
1
+ date: 20251006080454, utime: 1759737894