scanoss 1.19.5__py3-none-any.whl → 1.20.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 CHANGED
@@ -22,4 +22,4 @@
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = "1.19.5"
25
+ __version__ = "1.20.0"
@@ -0,0 +1,23 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
@@ -0,0 +1,23 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: scanoss/api/provenance/v2/scanoss-provenance.proto
4
+ # Protobuf Python Version: 4.25.1
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+
15
+ from scanoss.api.common.v2 import scanoss_common_pb2 as scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2
16
+ from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
17
+ from protoc_gen_swagger.options import annotations_pb2 as protoc__gen__swagger_dot_options_dot_annotations__pb2
18
+
19
+
20
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n2scanoss/api/provenance/v2/scanoss-provenance.proto\x12\x19scanoss.api.provenance.v2\x1a*scanoss/api/common/v2/scanoss-common.proto\x1a\x1cgoogle/api/annotations.proto\x1a,protoc-gen-swagger/options/annotations.proto\"\xd5\x03\n\x12ProvenanceResponse\x12\x42\n\x05purls\x18\x01 \x03(\x0b\x32\x33.scanoss.api.provenance.v2.ProvenanceResponse.Purls\x12\x35\n\x06status\x18\x02 \x01(\x0b\x32%.scanoss.api.common.v2.StatusResponse\x1a\x32\n\x10\x44\x65\x63laredLocation\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08location\x18\x02 \x01(\t\x1a\x31\n\x0f\x43uratedLocation\x12\x0f\n\x07\x63ountry\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\x1a\xdc\x01\n\x05Purls\x12\x0c\n\x04purl\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12Z\n\x12\x64\x65\x63lared_locations\x18\x03 \x03(\x0b\x32>.scanoss.api.provenance.v2.ProvenanceResponse.DeclaredLocation\x12X\n\x11\x63urated_locations\x18\x04 \x03(\x0b\x32=.scanoss.api.provenance.v2.ProvenanceResponse.CuratedLocation2\x98\x02\n\nProvenance\x12s\n\x04\x45\x63ho\x12\".scanoss.api.common.v2.EchoRequest\x1a#.scanoss.api.common.v2.EchoResponse\"\"\x82\xd3\xe4\x93\x02\x1c\"\x17/api/v2/provenance/echo:\x01*\x12\x94\x01\n\x16GetComponentProvenance\x12\".scanoss.api.common.v2.PurlRequest\x1a-.scanoss.api.provenance.v2.ProvenanceResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/api/v2/provenance/countries:\x01*B\x94\x02Z5github.com/scanoss/papi/api/provenancev2;provenancev2\x92\x41\xd9\x01\x12s\n\x1aSCANOSS Provenance Service\"P\n\x12scanoss-provenance\x12%https://github.com/scanoss/provenance\x1a\x13support@scanoss.com2\x03\x32.0*\x01\x01\x32\x10\x61pplication/json:\x10\x61pplication/jsonR;\n\x03\x34\x30\x34\x12\x34\n*Returned when the resource does not exist.\x12\x06\n\x04\x9a\x02\x01\x07\x62\x06proto3')
21
+
22
+ _globals = globals()
23
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
24
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'scanoss.api.provenance.v2.scanoss_provenance_pb2', _globals)
25
+ if _descriptor._USE_C_DESCRIPTORS == False:
26
+ _globals['DESCRIPTOR']._options = None
27
+ _globals['DESCRIPTOR']._serialized_options = b'Z5github.com/scanoss/papi/api/provenancev2;provenancev2\222A\331\001\022s\n\032SCANOSS Provenance Service\"P\n\022scanoss-provenance\022%https://github.com/scanoss/provenance\032\023support@scanoss.com2\0032.0*\001\0012\020application/json:\020application/jsonR;\n\003404\0224\n*Returned when the resource does not exist.\022\006\n\004\232\002\001\007'
28
+ _globals['_PROVENANCE'].methods_by_name['Echo']._options = None
29
+ _globals['_PROVENANCE'].methods_by_name['Echo']._serialized_options = b'\202\323\344\223\002\034\"\027/api/v2/provenance/echo:\001*'
30
+ _globals['_PROVENANCE'].methods_by_name['GetComponentProvenance']._options = None
31
+ _globals['_PROVENANCE'].methods_by_name['GetComponentProvenance']._serialized_options = b'\202\323\344\223\002!\"\034/api/v2/provenance/countries:\001*'
32
+ _globals['_PROVENANCERESPONSE']._serialized_start=202
33
+ _globals['_PROVENANCERESPONSE']._serialized_end=671
34
+ _globals['_PROVENANCERESPONSE_DECLAREDLOCATION']._serialized_start=347
35
+ _globals['_PROVENANCERESPONSE_DECLAREDLOCATION']._serialized_end=397
36
+ _globals['_PROVENANCERESPONSE_CURATEDLOCATION']._serialized_start=399
37
+ _globals['_PROVENANCERESPONSE_CURATEDLOCATION']._serialized_end=448
38
+ _globals['_PROVENANCERESPONSE_PURLS']._serialized_start=451
39
+ _globals['_PROVENANCERESPONSE_PURLS']._serialized_end=671
40
+ _globals['_PROVENANCE']._serialized_start=674
41
+ _globals['_PROVENANCE']._serialized_end=954
42
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,108 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+
5
+ from scanoss.api.common.v2 import scanoss_common_pb2 as scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2
6
+ from scanoss.api.provenance.v2 import scanoss_provenance_pb2 as scanoss_dot_api_dot_provenance_dot_v2_dot_scanoss__provenance__pb2
7
+
8
+
9
+ class ProvenanceStub(object):
10
+ """*
11
+ Expose all of the SCANOSS Provenance RPCs here
12
+ """
13
+
14
+ def __init__(self, channel):
15
+ """Constructor.
16
+
17
+ Args:
18
+ channel: A grpc.Channel.
19
+ """
20
+ self.Echo = channel.unary_unary(
21
+ '/scanoss.api.provenance.v2.Provenance/Echo',
22
+ request_serializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoRequest.SerializeToString,
23
+ response_deserializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoResponse.FromString,
24
+ )
25
+ self.GetComponentProvenance = channel.unary_unary(
26
+ '/scanoss.api.provenance.v2.Provenance/GetComponentProvenance',
27
+ request_serializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.PurlRequest.SerializeToString,
28
+ response_deserializer=scanoss_dot_api_dot_provenance_dot_v2_dot_scanoss__provenance__pb2.ProvenanceResponse.FromString,
29
+ )
30
+
31
+
32
+ class ProvenanceServicer(object):
33
+ """*
34
+ Expose all of the SCANOSS Provenance RPCs here
35
+ """
36
+
37
+ def Echo(self, request, context):
38
+ """Standard echo
39
+ """
40
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
41
+ context.set_details('Method not implemented!')
42
+ raise NotImplementedError('Method not implemented!')
43
+
44
+ def GetComponentProvenance(self, request, context):
45
+ """Get Provenance countrues associated with a list of PURLs
46
+ """
47
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
48
+ context.set_details('Method not implemented!')
49
+ raise NotImplementedError('Method not implemented!')
50
+
51
+
52
+ def add_ProvenanceServicer_to_server(servicer, server):
53
+ rpc_method_handlers = {
54
+ 'Echo': grpc.unary_unary_rpc_method_handler(
55
+ servicer.Echo,
56
+ request_deserializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoRequest.FromString,
57
+ response_serializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoResponse.SerializeToString,
58
+ ),
59
+ 'GetComponentProvenance': grpc.unary_unary_rpc_method_handler(
60
+ servicer.GetComponentProvenance,
61
+ request_deserializer=scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.PurlRequest.FromString,
62
+ response_serializer=scanoss_dot_api_dot_provenance_dot_v2_dot_scanoss__provenance__pb2.ProvenanceResponse.SerializeToString,
63
+ ),
64
+ }
65
+ generic_handler = grpc.method_handlers_generic_handler(
66
+ 'scanoss.api.provenance.v2.Provenance', rpc_method_handlers)
67
+ server.add_generic_rpc_handlers((generic_handler,))
68
+
69
+
70
+ # This class is part of an EXPERIMENTAL API.
71
+ class Provenance(object):
72
+ """*
73
+ Expose all of the SCANOSS Provenance RPCs here
74
+ """
75
+
76
+ @staticmethod
77
+ def Echo(request,
78
+ target,
79
+ options=(),
80
+ channel_credentials=None,
81
+ call_credentials=None,
82
+ insecure=False,
83
+ compression=None,
84
+ wait_for_ready=None,
85
+ timeout=None,
86
+ metadata=None):
87
+ return grpc.experimental.unary_unary(request, target, '/scanoss.api.provenance.v2.Provenance/Echo',
88
+ scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoRequest.SerializeToString,
89
+ scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.EchoResponse.FromString,
90
+ options, channel_credentials,
91
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
92
+
93
+ @staticmethod
94
+ def GetComponentProvenance(request,
95
+ target,
96
+ options=(),
97
+ channel_credentials=None,
98
+ call_credentials=None,
99
+ insecure=False,
100
+ compression=None,
101
+ wait_for_ready=None,
102
+ timeout=None,
103
+ metadata=None):
104
+ return grpc.experimental.unary_unary(request, target, '/scanoss.api.provenance.v2.Provenance/GetComponentProvenance',
105
+ scanoss_dot_api_dot_common_dot_v2_dot_scanoss__common__pb2.PurlRequest.SerializeToString,
106
+ scanoss_dot_api_dot_provenance_dot_v2_dot_scanoss__provenance__pb2.ProvenanceResponse.FromString,
107
+ options, channel_credentials,
108
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
scanoss/cli.py CHANGED
@@ -198,6 +198,13 @@ def setup_args() -> None:
198
198
  help='Retrieve semgrep issues/findings for the given components')
199
199
  c_semgrep.set_defaults(func=comp_semgrep)
200
200
 
201
+ # Component Sub-command: component provenance
202
+ c_provenance = comp_sub.add_parser('provenance', aliases=['prov', 'prv'],
203
+ description=f'Show Provenance findings: {__version__}',
204
+ help='Retrieve provenance for the given components')
205
+ c_provenance.set_defaults(func=comp_provenance)
206
+
207
+
201
208
  # Component Sub-command: component search
202
209
  c_search = comp_sub.add_parser('search', aliases=['sc'],
203
210
  description=f'Search component details: {__version__}',
@@ -221,11 +228,11 @@ def setup_args() -> None:
221
228
  c_versions.set_defaults(func=comp_versions)
222
229
 
223
230
  # Common purl Component sub-command options
224
- for p in [c_crypto, c_vulns, c_semgrep]:
231
+ for p in [c_crypto, c_vulns, c_semgrep, c_provenance]:
225
232
  p.add_argument('--purl', '-p', type=str, nargs="*", help='Package URL - PURL to process.')
226
233
  p.add_argument('--input', '-i', type=str, help='Input file name')
227
234
  # Common Component sub-command options
228
- for p in [c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
235
+ for p in [c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
229
236
  p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
230
237
  p.add_argument('--timeout', '-M', type=int, default=600,
231
238
  help='Timeout (in seconds) for API communication (optional - default 600)')
@@ -361,7 +368,7 @@ def setup_args() -> None:
361
368
  p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
362
369
 
363
370
  # Global Scan/GRPC options
364
- for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
371
+ for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
365
372
  p.add_argument('--key', '-k', type=str,
366
373
  help='SCANOSS API Key token (optional - not required for default OSSKB URL)')
367
374
  p.add_argument('--proxy', type=str, help='Proxy URL to use for connections (optional). '
@@ -375,7 +382,7 @@ def setup_args() -> None:
375
382
  '"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/cacert.pem" for gRPC')
376
383
 
377
384
  # Global GRPC options
378
- for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep]:
385
+ for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
379
386
  p.add_argument('--api2url', type=str,
380
387
  help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)')
381
388
  p.add_argument('--grpc-proxy', type=str, help='GRPC Proxy URL to use for connections (optional). '
@@ -383,7 +390,7 @@ def setup_args() -> None:
383
390
 
384
391
  # Help/Trace command options
385
392
  for p in [p_scan, p_wfp, p_dep, p_fc, p_cnv, p_c_loc, p_c_dwnld, p_p_proxy, c_crypto, c_vulns, c_search,
386
- c_versions, c_semgrep, p_results, p_undeclared, p_copyleft]:
393
+ c_versions, c_semgrep, p_results, p_undeclared, p_copyleft, c_provenance]:
387
394
  p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
388
395
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
389
396
  p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
@@ -473,7 +480,7 @@ def wfp(parser, args):
473
480
  if args.output:
474
481
  scan_output = args.output
475
482
  open(scan_output, 'w').close()
476
-
483
+
477
484
  # Load scan settings
478
485
  scan_settings = None
479
486
  if not args.skip_settings_file:
@@ -575,11 +582,11 @@ def scan(parser, args):
575
582
  scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
576
583
  try:
577
584
  if args.identify:
578
- scan_settings.load_json_file(args.identify).set_file_type('legacy').set_scan_type('identify')
585
+ scan_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type('identify')
579
586
  elif args.ignore:
580
- scan_settings.load_json_file(args.ignore).set_file_type('legacy').set_scan_type('blacklist')
587
+ scan_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type('blacklist')
581
588
  else:
582
- scan_settings.load_json_file(args.settings).set_file_type('new').set_scan_type('identify')
589
+ scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type('identify')
583
590
  except ScanossSettingsError as e:
584
591
  print_stderr(f'Error: {e}')
585
592
  exit(1)
@@ -1022,6 +1029,30 @@ def comp_semgrep(parser, args):
1022
1029
  if not comps.get_semgrep_details(args.input, args.purl, args.output):
1023
1030
  exit(1)
1024
1031
 
1032
+ def comp_provenance(parser, args):
1033
+ """
1034
+ Run the "component provenance" sub-command
1035
+ Parameters
1036
+ ----------
1037
+ parser: ArgumentParser
1038
+ command line parser object
1039
+ args: Namespace
1040
+ Parsed arguments
1041
+ """
1042
+ if (not args.purl and not args.input) or (args.purl and args.input):
1043
+ print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
1044
+ parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1045
+ exit(1)
1046
+ if args.ca_cert and not os.path.exists(args.ca_cert):
1047
+ print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
1048
+ exit(1)
1049
+ pac_file = get_pac_file(args.pac)
1050
+ comps = Components(debug=args.debug, trace=args.trace, quiet=args.quiet, grpc_url=args.api2url, api_key=args.key,
1051
+ ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, pac=pac_file,
1052
+ timeout=args.timeout)
1053
+ if not comps.get_provenance_details(args.input, args.purl, args.output):
1054
+ exit(1)
1055
+
1025
1056
  def comp_search(parser, args):
1026
1057
  """
1027
1058
  Run the "component search" sub-command
scanoss/components.py CHANGED
@@ -24,7 +24,7 @@
24
24
  import json
25
25
  import os
26
26
  import sys
27
- from typing import TextIO
27
+ from typing import TextIO, Optional, List
28
28
 
29
29
  from pypac.parser import PACFile
30
30
 
@@ -62,13 +62,13 @@ class Components(ScanossBase):
62
62
  ver_details=ver_details, ca_cert=ca_cert, proxy=proxy, pac=pac,
63
63
  grpc_proxy=grpc_proxy, timeout=timeout)
64
64
 
65
- def load_purls(self, json_file: str = None, purls: [] = None) -> dict:
65
+ def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
66
66
  """
67
67
  Load the specified purls and return a dictionary
68
68
 
69
69
  :param json_file: JSON PURL file (optional)
70
70
  :param purls: list of PURLs (optional)
71
- :return: PURL Request dictionary
71
+ :return: PURL Request dictionary or None
72
72
  """
73
73
  if json_file:
74
74
  if not os.path.isfile(json_file) or not os.access(json_file, os.R_OK):
@@ -81,6 +81,9 @@ class Components(ScanossBase):
81
81
  self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
82
82
  return None
83
83
  elif purls:
84
+ if not all(isinstance(purl, str) for purl in purls):
85
+ self.print_stderr('ERROR: PURLs must be a list of strings.')
86
+ return None
84
87
  parsed_purls = []
85
88
  for p in purls:
86
89
  parsed_purls.append({'purl': p})
@@ -302,3 +305,29 @@ class Components(ScanossBase):
302
305
  self.print_msg(f'Results written to: {output_file}')
303
306
  self._close_file(output_file, file)
304
307
  return success
308
+
309
+ def get_provenance_details(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
310
+ """
311
+ Retrieve the semgrep details for the supplied PURLs
312
+
313
+ :param json_file: PURL JSON request file (optional)
314
+ :param purls: PURL request array (optional)
315
+ :param output_file: output filename (optional). Default: STDOUT
316
+ :return: True on success, False otherwise
317
+ """
318
+ success = False
319
+ purls_request = self.load_purls(json_file, purls)
320
+ if purls_request is None or len(purls_request) == 0:
321
+ return False
322
+ file = self._open_file_or_sdtout(output_file)
323
+ if file is None:
324
+ return False
325
+ self.print_msg('Sending PURLs to Provenance API for decoration...')
326
+ response = self.grpc_api.get_provenance_json(purls_request)
327
+ if response:
328
+ print(json.dumps(response, indent=2, sort_keys=True), file=file)
329
+ success = True
330
+ if output_file:
331
+ self.print_msg(f'Results written to: {output_file}')
332
+ self._close_file(output_file, file)
333
+ return success
scanoss/cyclonedx.py CHANGED
@@ -197,12 +197,12 @@ class CycloneDx(ScanossBase):
197
197
  'name': 'scanoss-py',
198
198
  'version': __version__,
199
199
  }
200
- ]
201
- },
202
- 'component': {
203
- 'type': 'application',
204
- 'name': 'NOASSERTION',
205
- 'version': 'NOASSERTION'
200
+ ],
201
+ 'component': {
202
+ 'type': 'application',
203
+ 'name': 'NOASSERTION',
204
+ 'version': 'NOASSERTION'
205
+ }
206
206
  },
207
207
  'components': [],
208
208
  'vulnerabilities': []
@@ -1 +1 @@
1
- date: 20250115100233, utime: 1736935353
1
+ date: 20250205133848, utime: 1738762728
@@ -30,9 +30,9 @@ import importlib_resources
30
30
  from jsonschema import validate
31
31
 
32
32
  from .scanossbase import ScanossBase
33
- from .utils.file import validate_json_file
33
+ from .utils.file import JSON_ERROR_FILE_NOT_FOUND, JSON_ERROR_FILE_EMPTY, validate_json_file
34
34
 
35
- DEFAULT_SCANOSS_JSON_FILE = 'scanoss.json'
35
+ DEFAULT_SCANOSS_JSON_FILE = Path('scanoss.json')
36
36
 
37
37
 
38
38
  class BomEntry(TypedDict, total=False):
@@ -96,16 +96,20 @@ class ScanossSettings(ScanossBase):
96
96
  if filepath:
97
97
  self.load_json_file(filepath)
98
98
 
99
- def load_json_file(self, filepath: 'str | None' = None) -> 'ScanossSettings':
99
+ def load_json_file(self, filepath: 'str | None' = None, scan_root: 'str | None' = None) -> 'ScanossSettings':
100
100
  """
101
101
  Load the scan settings file. If no filepath is provided, scanoss.json will be used as default.
102
102
 
103
103
  Args:
104
104
  filepath (str): Path to the SCANOSS settings file
105
105
  """
106
+
106
107
  if not filepath:
107
108
  filepath = DEFAULT_SCANOSS_JSON_FILE
108
- json_file = Path(filepath).resolve()
109
+
110
+ filepath = Path(scan_root) / filepath if scan_root else Path(filepath)
111
+
112
+ json_file = filepath.resolve()
109
113
 
110
114
  if filepath == DEFAULT_SCANOSS_JSON_FILE and not json_file.exists():
111
115
  self.print_debug(f'Default settings file "{filepath}" not found. Skipping...')
@@ -114,7 +118,11 @@ class ScanossSettings(ScanossBase):
114
118
 
115
119
  result = validate_json_file(json_file)
116
120
  if not result.is_valid:
117
- raise ScanossSettingsError(f'Problem with settings file. {result.error}')
121
+ if result.error_code == JSON_ERROR_FILE_NOT_FOUND or result.error_code == JSON_ERROR_FILE_EMPTY:
122
+ self.print_msg(f'WARNING: The supplied settings file "{filepath}" was not found or is empty. Skipping...')
123
+ return self
124
+ else:
125
+ raise ScanossSettingsError(f'Problem with settings file. {result.error}')
118
126
  try:
119
127
  validate(result.data, self.schema)
120
128
  except Exception as e:
scanoss/scanossgrpc.py CHANGED
@@ -37,6 +37,7 @@ from .api.components.v2.scanoss_components_pb2_grpc import ComponentsStub
37
37
  from .api.cryptography.v2.scanoss_cryptography_pb2_grpc import CryptographyStub
38
38
  from .api.dependencies.v2.scanoss_dependencies_pb2_grpc import DependenciesStub
39
39
  from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
40
+ from .api.provenance.v2.scanoss_provenance_pb2_grpc import ProvenanceStub
40
41
  from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub
41
42
  from .api.cryptography.v2.scanoss_cryptography_pb2 import AlgorithmResponse
42
43
  from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest, DependencyResponse
@@ -45,6 +46,8 @@ from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityRes
45
46
  from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
46
47
  from .api.components.v2.scanoss_components_pb2 import (CompSearchRequest, CompSearchResponse,
47
48
  CompVersionRequest, CompVersionResponse)
49
+ from .api.provenance.v2.scanoss_provenance_pb2 import ProvenanceResponse
50
+
48
51
  from .scanossbase import ScanossBase
49
52
  from . import __version__
50
53
 
@@ -113,6 +116,7 @@ class ScanossGrpc(ScanossBase):
113
116
  self.dependencies_stub = DependenciesStub(grpc.insecure_channel(self.url))
114
117
  self.semgrep_stub = SemgrepStub(grpc.insecure_channel(self.url))
115
118
  self.vuln_stub = VulnerabilitiesStub(grpc.insecure_channel(self.url))
119
+ self.provenance_stub = ProvenanceStub(grpc.insecure_channel(self.url))
116
120
  else:
117
121
  if ca_cert is not None:
118
122
  credentials = grpc.ssl_channel_credentials(cert_data) # secure with specified certificate
@@ -123,6 +127,7 @@ class ScanossGrpc(ScanossBase):
123
127
  self.dependencies_stub = DependenciesStub(grpc.secure_channel(self.url, credentials))
124
128
  self.semgrep_stub = SemgrepStub(grpc.secure_channel(self.url, credentials))
125
129
  self.vuln_stub = VulnerabilitiesStub(grpc.secure_channel(self.url, credentials))
130
+ self.provenance_stub = ProvenanceStub(grpc.secure_channel(self.url, credentials))
126
131
 
127
132
  @classmethod
128
133
  def _load_cert(cls, cert_file: str) -> bytes:
@@ -383,13 +388,15 @@ class ScanossGrpc(ScanossBase):
383
388
  self.print_debug(f'Checking response status (rqId: {request_id}): {status_response}')
384
389
  status_code: StatusCode = status_response.status
385
390
  if status_code > 1:
391
+ ret_val = False # default to failed
386
392
  msg = "Unsuccessful"
387
393
  if status_code == 2:
388
394
  msg = "Succeeded with warnings"
395
+ ret_val = True # No need to fail as it succeeded with warnings
389
396
  elif status_code == 3:
390
397
  msg = "Failed with warnings"
391
398
  self.print_stderr(f'{msg} (rqId: {request_id} - status: {status_code}): {status_response.message}')
392
- return False
399
+ return ret_val
393
400
  return True
394
401
 
395
402
  def _get_proxy_config(self):
@@ -414,6 +421,33 @@ class ScanossGrpc(ScanossBase):
414
421
  os.environ["http_proxy"] = proxies.get("http") or ""
415
422
  os.environ["https_proxy"] = proxies.get("https") or ""
416
423
 
424
+ def get_provenance_json(self, purls: dict) -> dict:
425
+ """
426
+ Client function to call the rpc for GetComponentProvenance
427
+ :param purls: Message to send to the service
428
+ :return: Server response or None
429
+ """
430
+ if not purls:
431
+ self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
432
+ return None
433
+ request_id = str(uuid.uuid4())
434
+ resp: ProvenanceResponse
435
+ try:
436
+ request = ParseDict(purls, PurlRequest()) # Parse the JSON/Dict into the purl request object
437
+ metadata = self.metadata[:]
438
+ metadata.append(('x-request-id', request_id)) # Set a Request ID
439
+ self.print_debug(f'Sending data for provenance decoration (rqId: {request_id})...')
440
+ resp = self.provenance_stub.GetComponentProvenance(request, metadata=metadata, timeout=self.timeout)
441
+ except Exception as e:
442
+ self.print_stderr(f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message '
443
+ f'(rqId: {request_id}): {e}')
444
+ else:
445
+ if resp:
446
+ if not self._check_status_response(resp.status, request_id):
447
+ return None
448
+ resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
449
+ return resp_dict
450
+ return None
417
451
  #
418
452
  # End of ScanossGrpc Class
419
453
  #
scanoss/utils/file.py CHANGED
@@ -21,18 +21,24 @@ SPDX-License-Identifier: MIT
21
21
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
  THE SOFTWARE.
23
23
  """
24
+
24
25
  import json
25
26
  import os
26
- import sys
27
27
  from dataclasses import dataclass
28
28
  from typing import Optional
29
29
 
30
+ JSON_ERROR_PARSE = 1
31
+ JSON_ERROR_FILE_NOT_FOUND = 2
32
+ JSON_ERROR_FILE_EMPTY = 3
33
+ JSON_ERROR_FILE_SIZE = 4
34
+
30
35
 
31
36
  @dataclass
32
37
  class JsonValidation:
33
38
  is_valid: bool
34
39
  data: Optional[dict] = None
35
40
  error: Optional[str] = None
41
+ error_code: Optional[int] = None
36
42
 
37
43
 
38
44
  def validate_json_file(json_file_path: str) -> JsonValidation:
@@ -46,12 +52,33 @@ def validate_json_file(json_file_path: str) -> JsonValidation:
46
52
  Tuple[bool, str]: A tuple containing a boolean indicating if the file is valid and a message
47
53
  """
48
54
  if not json_file_path:
49
- return JsonValidation(is_valid=False, error='No JSON file specified')
55
+ return JsonValidation(is_valid=False, error="No JSON file specified")
50
56
  if not os.path.isfile(json_file_path):
51
- return JsonValidation(is_valid=False, error=f'File not found: {json_file_path}')
57
+ return JsonValidation(
58
+ is_valid=False,
59
+ error=f"File not found: {json_file_path}",
60
+ error_code=JSON_ERROR_FILE_NOT_FOUND,
61
+ )
62
+ try:
63
+ if os.stat(json_file_path).st_size == 0:
64
+ return JsonValidation(
65
+ is_valid=False,
66
+ error=f"File is empty: {json_file_path}",
67
+ error_code=JSON_ERROR_FILE_EMPTY,
68
+ )
69
+ except OSError as e:
70
+ return JsonValidation(
71
+ is_valid=False,
72
+ error=f"Problem checking file size: {json_file_path}: {e}",
73
+ error_code=JSON_ERROR_FILE_SIZE,
74
+ )
52
75
  try:
53
76
  with open(json_file_path) as f:
54
77
  data = json.load(f)
55
78
  return JsonValidation(is_valid=True, data=data)
56
79
  except json.JSONDecodeError as e:
57
- return JsonValidation(is_valid=False, error=f'Problem parsing JSON file: "{json_file_path}": {e}')
80
+ return JsonValidation(
81
+ is_valid=False,
82
+ error=f'Problem parsing JSON file: "{json_file_path}": {e}',
83
+ error_code=JSON_ERROR_PARSE,
84
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: scanoss
3
- Version: 1.19.5
3
+ Version: 1.20.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -4,20 +4,20 @@ protoc_gen_swagger/options/annotations_pb2.py,sha256=b25EDD6gssUWnFby9gxgcpLIROT
4
4
  protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
5
5
  protoc_gen_swagger/options/openapiv2_pb2.py,sha256=vYElGp8E1vGHszvWqX97zNG9GFJ7u2QcdK9ouq0XdyI,14939
6
6
  protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
7
- scanoss/__init__.py,sha256=ij90NBTyzIa-T9qnrk6hM4-mGPQ3ZmoNQPUBqaqJYOA,1163
8
- scanoss/cli.py,sha256=eJB6s1DVKJd3FPO7wVea8ggeAfua_-7CC8I10uSmdDc,51509
9
- scanoss/components.py,sha256=ZHZ1KA69shxOASZK7USD9yPTITpAc_RXL5q5zpDK23o,12590
7
+ scanoss/__init__.py,sha256=cs78w1v2fZ3JVQdN5zxPVWRoM_AcHHRAo26bCr0R63k,1163
8
+ scanoss/cli.py,sha256=pQwdt-LQGc5Iy3WKffYmtFcyM93LOILpiDyEPlVHggU,53034
9
+ scanoss/components.py,sha256=u7x3tdCQUTvbB97wTSuwfjtrhW6BsFdkeQqUmPZGPF4,13957
10
10
  scanoss/csvoutput.py,sha256=hBwr_Fc6mBdOdXgyQcdFrockYH-PJ0jblowlExJ6OPg,9925
11
- scanoss/cyclonedx.py,sha256=ZbFNinMz_R68ko4-ZPo2w4bBORpASh271VCmYYRTUCg,12746
11
+ scanoss/cyclonedx.py,sha256=QWDNvn6o_Y6xWYEr0-qDgDtNOTaHccxPeHFoNZLx0is,12766
12
12
  scanoss/file_filters.py,sha256=HGGECJ-P0LL8pg8YVO4BP8ql24tCnanx0_S2wA8EWis,16642
13
13
  scanoss/filecount.py,sha256=o7xb6m387ucnsU4H1OXGzf_AdWsudhAHe49T8uX4Ieo,6660
14
14
  scanoss/results.py,sha256=7G33QAYYI9qI61TCzXjSLYXMmg5CDtZS5e2QhnQfE74,9883
15
15
  scanoss/scancodedeps.py,sha256=_9d7MAV20-FrET7mF7gW-BZiz2eHrtwudgrEcSX0oZQ,11321
16
16
  scanoss/scanner.py,sha256=hF8N8XXSwYw93htpqv0h0t-IqswG_bcxH7awe4JA5Aw,45374
17
- scanoss/scanoss_settings.py,sha256=AJKMJeLoPVLO15vb9Pd12b5tVCa3ALP5sOXeWR6KCb0,10144
17
+ scanoss/scanoss_settings.py,sha256=VwcvbUYjhPv6svBhtOlxULrY3TnUZgLOlcmaU_f2jTk,10590
18
18
  scanoss/scanossapi.py,sha256=TJxPctr-0DTn_26LfM__OAMfntaXzvheFTbdmU-5pnM,11953
19
19
  scanoss/scanossbase.py,sha256=zMDRCLbrcoRvYEKQRuZXnBiVY4_Vsplmg_APbB65oaU,3084
20
- scanoss/scanossgrpc.py,sha256=ythZkr6F0P0hl_KPYoHkos_IL97TxLKeYfAouX_CUnM,20491
20
+ scanoss/scanossgrpc.py,sha256=NsHd8eIDOVcMcdR_o1gAKQu9d-O0z0qUwrOJ3BlrooA,22348
21
21
  scanoss/scanpostprocessor.py,sha256=OY75PLUMRas7hyjrHTCKiu4X-N_Z5t710wc9f3HPHqw,11074
22
22
  scanoss/scantype.py,sha256=R2-ExLGOrYxaJFtIK2AEo2caD0XrN1zpF5q1qT9Zsyc,1326
23
23
  scanoss/spdxlite.py,sha256=REChAWV-6qhp16jc4X2lMb1v7VvYiDH5nN9VDV3fDaQ,15828
@@ -39,6 +39,10 @@ scanoss/api/dependencies/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70
39
39
  scanoss/api/dependencies/v2/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
40
40
  scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py,sha256=mHeRlzUEHFFWQyJnpoJ7MG86p8Gqc64lfPG2nPK1n9A,5118
41
41
  scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py,sha256=JknWEAxZX21YyYjXs8BL2IUp52wqf_2a1Tn337i_3-g,4815
42
+ scanoss/api/provenance/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
43
+ scanoss/api/provenance/v2/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
44
+ scanoss/api/provenance/v2/scanoss_provenance_pb2.py,sha256=cxcyIWxhYzjjRPdgTeEfLdOA3XfI_SuYi1OMZMv1c-Q,4443
45
+ scanoss/api/provenance/v2/scanoss_provenance_pb2_grpc.py,sha256=87EYBU4fiVEQ8hDkOTzP29KgNPAbxDgEV85pZSPqPXE,4799
42
46
  scanoss/api/scanning/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
43
47
  scanoss/api/scanning/v2/__init__.py,sha256=KlDD87JmyZP-10T-fuJo0_v2zt1gxWfTgs70wjky9xg,1139
44
48
  scanoss/api/scanning/v2/scanoss_scanning_pb2.py,sha256=bod1HOOmiewM8gLH7LAwmK5BNAm0NxUoUwUZb9zs8LY,2550
@@ -51,7 +55,7 @@ scanoss/api/vulnerabilities/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEK
51
55
  scanoss/api/vulnerabilities/v2/__init__.py,sha256=FLQtiDiv85Q1Chk-sJ9ky9WOV1mulZhEKjiBihlwiaM,1139
52
56
  scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=CFhF80av8tenGvn9AIsGEtRJPuV2dC_syA5JLZb2lDw,5464
53
57
  scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=HlS4k4Zmx6RIAqaO9I96jD-eyF5yU6Xx04pVm7pdqOg,6864
54
- scanoss/data/build_date.txt,sha256=cD2H2wGDDcDtyTea3nvPIpKA91zDg5EYQmQHMvg14CM,40
58
+ scanoss/data/build_date.txt,sha256=SOOdGv4jmfsYyqJ4HbzIPZoLJcG2RLXv3tyKuaOnX8E,40
55
59
  scanoss/data/scanoss-settings-schema.json,sha256=ClkRYAkjAN0Sk704G8BE_Ok006oQ6YnIGmX84CF8h9w,8798
56
60
  scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
57
61
  scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
@@ -61,10 +65,10 @@ scanoss/inspection/policy_check.py,sha256=mbtNwDydf2aBUCz9n2yNG6DV6rc7HH_ZaQuK2C
61
65
  scanoss/inspection/undeclared_component.py,sha256=QvlBgYpytQhaWlVqrz3p8JPIGbsX8v8U59n-4Q2llLc,9951
62
66
  scanoss/inspection/utils/license_utils.py,sha256=mIaoVWXMA6shkRQmgmiP2mWchjxX4ex8LWs91Nf6rq4,5093
63
67
  scanoss/utils/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,1122
64
- scanoss/utils/file.py,sha256=W-XFLgaTM_td31Y5rzd5DimO4_qXafQhBtKtY_p3JIQ,2184
65
- scanoss-1.19.5.dist-info/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
66
- scanoss-1.19.5.dist-info/METADATA,sha256=wW-1T7CwxhTVqjzRO1YqCmups_yoJ2F7_E0yEtk75mI,6019
67
- scanoss-1.19.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
68
- scanoss-1.19.5.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
69
- scanoss-1.19.5.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
70
- scanoss-1.19.5.dist-info/RECORD,,
68
+ scanoss/utils/file.py,sha256=0Dv0Si8qVOGjauOboqos_caZhLR6qc6qEyKZDNwG3VA,2933
69
+ scanoss-1.20.0.dist-info/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
70
+ scanoss-1.20.0.dist-info/METADATA,sha256=KwnfB_BgcJot2j6MMJfUVabjTiwWOg0UUiTNREb0cjg,6019
71
+ scanoss-1.20.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
72
+ scanoss-1.20.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
73
+ scanoss-1.20.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
74
+ scanoss-1.20.0.dist-info/RECORD,,