From a4b3d0a4d5680bf9f21234eea94290a29c9b37bf Mon Sep 17 00:00:00 2001 From: eeisegn Date: Fri, 29 Aug 2025 17:46:14 +0100 Subject: [PATCH 1/4] add REST support for dependencies and vulnerabilities --- src/scanoss/cli.py | 7 +- src/scanoss/components.py | 35 ++++-- src/scanoss/scanner.py | 3 + src/scanoss/scanossgrpc.py | 244 ++++++++++++++++++++++++++++--------- 4 files changed, 223 insertions(+), 66 deletions(-) diff --git a/src/scanoss/cli.py b/src/scanoss/cli.py index c3aee759..983d773d 100644 --- a/src/scanoss/cli.py +++ b/src/scanoss/cli.py @@ -308,6 +308,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915 help='Retrieve vulnerabilities for the given components', ) c_vulns.set_defaults(func=comp_vulns) + c_vulns.add_argument('--grpc', action='store_true', help='Enable gRPC support') # Component Sub-command: component semgrep c_semgrep = comp_sub.add_parser( @@ -964,7 +965,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915 p.add_argument( '--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)' ) - p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors') + p.add_argument('--grpc', action='store_true', help='Enable gRPC support') # Global Scan/Fingerprint filter options for p in [p_scan, p_wfp]: @@ -1055,6 +1056,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915 type=str, help='Headers to be sent on request (e.g., -hdr "Name: Value") - can be used multiple times', ) + p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors') # Syft options for p in [p_cs, p_dep]: @@ -1418,6 +1420,7 @@ def scan(parser, args): # noqa: PLR0912, PLR0915 strip_snippet_ids=args.strip_snippet, scan_settings=scan_settings, req_headers=process_req_headers(args.header), + use_grpc=args.grpc ) if args.wfp: if not scanner.is_file_or_snippet_scan(): @@ -2144,6 +2147,8 @@ def comp_vulns(parser, args): pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), + ignore_cert_errors=args.ignore_cert_errors, + use_grpc=args.grpc, ) if not comps.get_vulnerabilities(args.input, args.purl, args.output): sys.exit(1) diff --git a/src/scanoss/components.py b/src/scanoss/components.py index c68a2336..6756fc82 100644 --- a/src/scanoss/components.py +++ b/src/scanoss/components.py @@ -52,6 +52,8 @@ def __init__( # noqa: PLR0913, PLR0915 ca_cert: str = None, pac: PACFile = None, req_headers: dict = None, + ignore_cert_errors: bool = False, + use_grpc: bool = False, ): """ Handle all component style requests @@ -66,6 +68,9 @@ def __init__( # noqa: PLR0913, PLR0915 :param grpc_proxy: Specific gRPC proxy (optional) :param ca_cert: TLS client certificate (optional) :param pac: Proxy Auto-Config file (optional) + :param req_headers: Additional headers to send with requests (optional) + :param ignore_cert_errors: Ignore TLS certificate errors (optional) + :param use_grpc: Use gRPC instead of HTTP (optional) """ super().__init__(debug, trace, quiet) ver_details = Scanner.version_details() @@ -82,14 +87,28 @@ def __init__( # noqa: PLR0913, PLR0915 grpc_proxy=grpc_proxy, timeout=timeout, req_headers=req_headers, + ignore_cert_errors=ignore_cert_errors, + use_grpc=use_grpc, ) - def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]: + def load_comps(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None)-> Optional[dict]: + """ + Load the specified components and return a dictionary + + :param json_file: JSON Components file (optional) + :param purls: list pf PURLs (optional) + :return: Components Request dictionary or None + """ + return self.load_purls(json_file, purls, 'components') + + def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None, field:str = 'purls' + ) -> Optional[dict]: """ Load the specified purls and return a dictionary :param json_file: JSON PURL file (optional) :param purls: list of PURLs (optional) + :param field: Name of the dictionary field to store the purls in (default: 'purls') :return: PURL Request dictionary or None """ if json_file: @@ -109,14 +128,14 @@ def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] parsed_purls = [] for p in purls: parsed_purls.append({'purl': p}) - purl_request = {'purls': parsed_purls} + purl_request = {field: parsed_purls} else: self.print_stderr('ERROR: No purls specified to process.') return None - purl_count = len(purl_request.get('purls', [])) - self.print_debug(f'Parsed Purls ({purl_count}): {purl_request}') + purl_count = len(purl_request.get(field, [])) + self.print_debug(f'Parsed {field} ({purl_count}): {purl_request}') if purl_count == 0: - self.print_stderr('ERROR: No PURLs parsed from request.') + self.print_stderr(f'ERROR: No {field} parsed from request.') return None return purl_request @@ -142,8 +161,8 @@ def _open_file_or_sdtout(self, filename): """ Open the given filename if requested, otherwise return STDOUT - :param filename: - :return: + :param filename: filename to open or None to return STDOUT + :return: file descriptor or None """ file = sys.stdout if filename: @@ -202,7 +221,7 @@ def get_vulnerabilities(self, json_file: str = None, purls: [] = None, output_fi :return: True on success, False otherwise """ success = False - purls_request = self.load_purls(json_file, purls) + purls_request = self.load_comps(json_file, purls) if purls_request is None or len(purls_request) == 0: return False file = self._open_file_or_sdtout(output_file) diff --git a/src/scanoss/scanner.py b/src/scanoss/scanner.py index 76767ce7..63803e54 100644 --- a/src/scanoss/scanner.py +++ b/src/scanoss/scanner.py @@ -107,6 +107,7 @@ def __init__( # noqa: PLR0913, PLR0915 skip_md5_ids=None, scan_settings: 'ScanossSettings | None' = None, req_headers: dict = None, + use_grpc: bool = False, ): """ Initialise scanning class, including Winnowing, ScanossApi, ThreadedScanning @@ -173,6 +174,8 @@ def __init__( # noqa: PLR0913, PLR0915 pac=pac, grpc_proxy=grpc_proxy, req_headers=self.req_headers, + ignore_cert_errors=ignore_cert_errors, + use_grpc=use_grpc ) self.threaded_deps = ThreadedDependencies(sc_deps, grpc_api, debug=debug, quiet=quiet, trace=trace) self.nb_threads = nb_threads diff --git a/src/scanoss/scanossgrpc.py b/src/scanoss/scanossgrpc.py index 4c11f715..a8554a18 100644 --- a/src/scanoss/scanossgrpc.py +++ b/src/scanoss/scanossgrpc.py @@ -24,17 +24,25 @@ import concurrent.futures import json +import logging import os +import sys +import time import uuid from dataclasses import dataclass from enum import IntEnum +import requests from typing import Dict, Optional from urllib.parse import urlparse +import http.client as http_client +import urllib3 import grpc from google.protobuf.json_format import MessageToDict, ParseDict +from pypac import PACSession from pypac.parser import PACFile from pypac.resolver import ProxyResolver +from urllib3.exceptions import InsecureRequestWarning from scanoss.api.scanning.v2.scanoss_scanning_pb2_grpc import ScanningStub from scanoss.constants import DEFAULT_TIMEOUT @@ -46,6 +54,7 @@ PurlRequest, StatusCode, StatusResponse, + ComponentsRequest, ) from .api.components.v2.scanoss_components_pb2 import ( CompSearchRequest, @@ -62,7 +71,7 @@ from .api.scanning.v2.scanoss_scanning_pb2 import HFHRequest from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub -from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityResponse +from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import ComponentsVulnerabilityResponse from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub from .scanossbase import ScanossBase @@ -70,21 +79,20 @@ DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL SCANOSS_GRPC_URL = os.environ.get('SCANOSS_GRPC_URL') if os.environ.get('SCANOSS_GRPC_URL') else DEFAULT_URL SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_API_KEY') else '' +DEFAULT_URI_PREFIX = '/v2' -MAX_CONCURRENT_REQUESTS = 5 +MAX_CONCURRENT_REQUESTS = 5 # Maximum number of concurrent requests to make class ScanossGrpcError(Exception): """ Custom exception for SCANOSS gRPC errors """ - pass class ScanossGrpcStatusCode(IntEnum): """Status codes for SCANOSS gRPC responses""" - SUCCESS = 1 SUCCESS_WITH_WARNINGS = 2 FAILED_WITH_WARNINGS = 3 @@ -110,6 +118,8 @@ def __init__( # noqa: PLR0913, PLR0915 grpc_proxy: str = None, pac: PACFile = None, req_headers: dict = None, + ignore_cert_errors: bool = False, + use_grpc: bool = False, ): """ @@ -133,21 +143,49 @@ def __init__( # noqa: PLR0913, PLR0915 self.proxy = proxy self.grpc_proxy = grpc_proxy self.pac = pac - self.req_headers = req_headers self.metadata = [] + self.ignore_cert_errors = ignore_cert_errors + self.use_grpc = use_grpc + self.req_headers = req_headers if req_headers else {} + self.headers = {} + self.retry_limit = 2 # default retry limit if self.api_key: self.metadata.append(('x-api-key', api_key)) # Set API key if we have one + self.headers['X-Session'] = self.api_key + self.headers['x-api-key'] = self.api_key if ver_details: self.metadata.append(('x-scanoss-client', ver_details)) + self.headers['x-scanoss-client'] = ver_details self.metadata.append(('user-agent', f'scanoss-py/{__version__}')) + self.headers['User-Agent'] = f'scanoss-py/{__version__}' + self.headers['user-agent'] = f'scanoss-py/{__version__}' + self.headers['Content-Type'] = 'application/json' self.load_generic_headers() self.url = url if url else SCANOSS_GRPC_URL if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'): self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium self.url = self.url.lower() - self.orig_url = self.url # Used for proxy lookup + self.orig_url = self.url.strip().rstrip('/') # Used for proxy lookup + # REST setup + if self.trace: + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + http_client.HTTPConnection.debuglevel = 1 + if pac and not proxy: # Set up a PAC session if requested (and no proxy has been explicitly set) + self.print_debug('Setting up PAC session...') + self.session = PACSession(pac=pac) + else: + self.session = requests.sessions.Session() + if self.ignore_cert_errors: + self.print_debug('Ignoring cert errors...') + urllib3.disable_warnings(InsecureRequestWarning) + self.session.verify = False + elif ca_cert: + self.session.verify = ca_cert + self.proxies = {'https': proxy, 'http': proxy} if proxy else None + if self.proxies: + self.session.proxies = self.proxies secure = True if self.url.startswith('https:') else False # Is it a secure connection? if self.url.startswith('http'): @@ -162,7 +200,7 @@ def __init__( # noqa: PLR0913, PLR0915 cert_data = ScanossGrpc._load_cert(ca_cert) self.print_debug(f'Setting up (secure: {secure}) connection to {self.url}...') self._get_proxy_config() - if secure is False: # insecure connection + if not secure: # insecure connection self.comp_search_stub = ComponentsStub(grpc.insecure_channel(self.url)) self.crypto_stub = CryptographyStub(grpc.insecure_channel(self.url)) self.dependencies_stub = DependenciesStub(grpc.insecure_channel(self.url)) @@ -206,17 +244,6 @@ def deps_echo(self, message: str = 'Hello there!') -> str: f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}' ) else: - # self.print_stderr(f'resp: {resp} - call: {call}') - # response_id = "" - # if not call: - # self.print_stderr(f'No call to leverage.') - # for key, value in call.trailing_metadata(): - # print('Greeter client received trailing metadata: key=%s value=%s' % (key, value)) - # - # for key, value in call.trailing_metadata(): - # if key == 'x-response-id': - # response_id = value - # self.print_stderr(f'Response ID: {response_id}. Metadata: {call.trailing_metadata()}') if resp: return resp.message self.print_stderr(f'ERROR: Problem sending Echo request ({message}) to {self.url}. rqId: {request_id}') @@ -264,54 +291,70 @@ def get_dependencies_json(self, dependencies: dict, depth: int = 1) -> dict: if not dependencies: self.print_stderr('ERROR: No message supplied to send to gRPC service.') return None - files_json = dependencies.get('files') - if files_json is None or len(files_json) == 0: - self.print_stderr('ERROR: No dependency data supplied to send to gRPC service.') + self.print_stderr('ERROR: No dependency data supplied to send to decoration service.') return None - - def process_file(file): - request_id = str(uuid.uuid4()) - try: - file_request = {'files': [file]} - - request = ParseDict(file_request, DependencyRequest()) - request.depth = depth - metadata = self.metadata[:] - metadata.append(('x-request-id', request_id)) - self.print_debug(f'Sending dependency data for decoration (rqId: {request_id})...') - resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout) - - return MessageToDict(resp, preserving_proto_field_name=True) - except Exception as e: - self.print_stderr( - f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}' - ) - return None - all_responses = [] + # determine if we are using gRPC or REST based on the use_grpc flag + process_file = self._process_dep_file_grpc if self.use_grpc else self._process_dep_file_rest + # Process the dependency files in parallel with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT_REQUESTS) as executor: - future_to_file = {executor.submit(process_file, file): file for file in files_json} - + future_to_file = {executor.submit(process_file, file, depth): file for file in files_json} for future in concurrent.futures.as_completed(future_to_file): response = future.result() if response: all_responses.append(response) - - SUCCESS_STATUS = 'SUCCESS' - - merged_response = {'files': [], 'status': {'status': SUCCESS_STATUS, 'message': 'Success'}} + # End of concurrent processing + success_status = 'SUCCESS' + merged_response = {'files': [], 'status': {'status': success_status, 'message': 'Success'}} + # Merge the responses for response in all_responses: if response: if 'files' in response and len(response['files']) > 0: merged_response['files'].append(response['files'][0]) - # Overwrite the status if the any of the responses was not successful - if 'status' in response and response['status']['status'] != SUCCESS_STATUS: + # Overwrite the status if any of the responses was not successful + if 'status' in response and response['status']['status'] != success_status: merged_response['status'] = response['status'] return merged_response + def _process_dep_file_grpc(self, file, depth: int = 1) -> dict: + """ + Process a single file using gRPC + + :param file: dependency file purls + :param depth: depth to search (default: 1) + :return: response JSON or None + """ + request_id = str(uuid.uuid4()) + try: + file_request = {'files': [file]} + request = ParseDict(file_request, DependencyRequest()) + request.depth = depth + metadata = self.metadata[:] + metadata.append(('x-request-id', request_id)) + self.print_debug(f'Sending dependency data via gRPC for decoration (rqId: {request_id})...') + resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout) + return MessageToDict(resp, preserving_proto_field_name=True) + except Exception as e: + self.print_stderr( + f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}' + ) + return None + def get_vulnerabilities_json(self, purls: dict) -> dict: + """ + Client function to call the rpc for Vulnerability GetVulnerabilities + It will either use REST (default) or gRPC depending on the use_grpc flag + :param purls: Message to send to the service + :return: Server response or None + """ + if self.use_grpc: + return self._get_vulnerabilities_grpc(purls) + else: + return self._get_vulnerabilities_rest(purls) + + def _get_vulnerabilities_grpc(self, purls: dict) -> dict: """ Client function to call the rpc for Vulnerability GetVulnerabilities :param purls: Message to send to the service @@ -321,13 +364,13 @@ def get_vulnerabilities_json(self, purls: dict) -> dict: self.print_stderr('ERROR: No message supplied to send to gRPC service.') return None request_id = str(uuid.uuid4()) - resp: VulnerabilityResponse + resp: ComponentsVulnerabilityResponse try: - request = ParseDict(purls, PurlRequest()) # Parse the JSON/Dict into the purl request object + request = ParseDict(purls, ComponentsRequest()) # Parse the JSON/Dict into the purl request object metadata = self.metadata[:] metadata.append(('x-request-id', request_id)) # Set a Request ID self.print_debug(f'Sending vulnerability data for decoration (rqId: {request_id})...') - resp = self.vuln_stub.GetVulnerabilities(request, metadata=metadata, timeout=self.timeout) + resp = self.vuln_stub.GetComponentsVulnerabilities(request, metadata=metadata, timeout=self.timeout) except Exception as e: self.print_stderr( f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}' @@ -462,14 +505,11 @@ def _call_rpc(self, rpc_method, request_input, request_type, debug_msg: Optional dict: The parsed gRPC response as a dictionary, or None if something went wrong """ request_id = str(uuid.uuid4()) - if isinstance(request_input, dict): request_obj = ParseDict(request_input, request_type()) else: request_obj = request_input - metadata = self.metadata[:] + [('x-request-id', request_id)] - self.print_debug(debug_msg.format(rqId=request_id)) try: resp = rpc_method(request_obj, metadata=metadata, timeout=self.timeout) @@ -477,7 +517,6 @@ def _call_rpc(self, rpc_method, request_input, request_type, debug_msg: Optional raise ScanossGrpcError( f'{e.__class__.__name__} while sending gRPC message (rqId: {request_id}): {e.details()}' ) - if resp and not self._check_status_response(resp.status, request_id): return None @@ -675,13 +714,100 @@ def load_generic_headers(self): self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium self.api_key = value self.metadata.append((key, value)) + self.headers[key] = value + + # + # End of gRPC Client Functions + # + # Start of REST Client Functions + # + + def rest_post(self, uri: str, request_id: str, data: dict) -> dict: + """ + Post the specified data to the given URI. + :param request_id: request id + :param uri: URI to post to + :param data: json data to post + :return: JSON response or None + """ + if not uri: + self.print_stderr('Error: Missing URI. Cannot search for project.') + return None + self.print_trace(f'Sending REST POST data to {uri}...') + headers = self.headers + headers['x-request-id'] = request_id # send a unique request id for each post + retry = 0 # Add some retry logic to cater for timeouts, etc. + while retry <= self.retry_limit: + retry += 1 + try: + response = self.session.post(uri, headers=headers, json=data, timeout=self.timeout) + response.raise_for_status() # Raises an HTTPError for bad responses + return response.json() + except requests.exceptions.RequestException as e: + self.print_stderr(f'Error: Problem posting data to {uri}: {e}') + except (requests.exceptions.SSLError, requests.exceptions.ProxyError) as e: + self.print_stderr(f'ERROR: Exception ({e.__class__.__name__}) POSTing data - {e}.') + raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e + except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e: + if retry > self.retry_limit: # Timed out retry_limit or more times, fail + self.print_stderr(f'ERROR: {e.__class__.__name__} POSTing decoration data ({request_id}): {e}') + raise Exception( + f'ERROR: The SCANOSS Decoration API request timed out ({e.__class__.__name__}) for {uri}' + ) from e + else: + self.print_stderr(f'Warning: {e.__class__.__name__} communicating with {self.url}. Retrying...') + time.sleep(5) + except Exception as e: + self.print_stderr( + f'ERROR: Exception ({e.__class__.__name__}) POSTing data ({request_id}) to {uri}: {e}' + ) + raise Exception(f'ERROR: The SCANOSS Decoration API request failed for {uri}') from e + return None + + def _get_vulnerabilities_rest(self, purls: dict): + """ + Get the vulnerabilities for the given purls using REST API + :param purls: Purl Request dictionary + :return: Vulnerability Response, or None if the request was unsuccessful + """ + if not purls: + self.print_stderr('ERROR: No message supplied to send to REST decoration service.') + return None + request_id = str(uuid.uuid4()) + self.print_debug(f'Sending data for Vulnerabilities via REST (request id: {request_id})...') + response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/vulnerabilities/components', request_id, purls) + self.print_trace(f'Received response for Vulnerabilities via REST (request id: {request_id}): {response}') + if response: + # Parse the JSON/Dict into the purl response + resp_obj = ParseDict(response, ComponentsVulnerabilityResponse(), True) + if resp_obj: + self.print_debug(f'Vulnerability Response: {resp_obj}') + if not self._check_status_response(resp_obj.status, request_id): + return None + del response['status'] + return response + return None + def _process_dep_file_rest(self, file, depth: int = 1) -> dict: + """ + Porcess a single dependency file using REST + :param file: dependency file purls + :param depth: depth to search (default: 1) + :return: response JSON or None + """ + request_id = str(uuid.uuid4()) + self.print_debug(f'Sending data for Dependencies via REST (request id: {request_id})...') + file_request = {'files': [file], 'depth': depth} + response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/dependencies/dependencies', request_id, file_request) + self.print_trace(f'Received response for Dependencies via REST (request id: {request_id}): {response}') + if response: + return response + return None # # End of ScanossGrpc Class # - @dataclass class GrpcConfig: url: str = DEFAULT_URL @@ -711,3 +837,7 @@ def create_grpc_config_from_args(args) -> GrpcConfig: proxy=getattr(args, 'proxy', None), grpc_proxy=getattr(args, 'grpc_proxy', None), ) + +# +# End of GrpcConfig class +# \ No newline at end of file From a3e8fe3d002805bd601c49ae4a560c1e704c7247 Mon Sep 17 00:00:00 2001 From: eeisegn Date: Fri, 29 Aug 2025 17:49:33 +0100 Subject: [PATCH 2/4] fix linter issues --- src/scanoss/scanossgrpc.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/scanoss/scanossgrpc.py b/src/scanoss/scanossgrpc.py index a8554a18..1c3c9e50 100644 --- a/src/scanoss/scanossgrpc.py +++ b/src/scanoss/scanossgrpc.py @@ -23,6 +23,7 @@ """ import concurrent.futures +import http.client as http_client import json import logging import os @@ -31,13 +32,12 @@ import uuid from dataclasses import dataclass from enum import IntEnum -import requests from typing import Dict, Optional from urllib.parse import urlparse -import http.client as http_client -import urllib3 import grpc +import requests +import urllib3 from google.protobuf.json_format import MessageToDict, ParseDict from pypac import PACSession from pypac.parser import PACFile @@ -49,12 +49,12 @@ from . import __version__ from .api.common.v2.scanoss_common_pb2 import ( + ComponentsRequest, EchoRequest, EchoResponse, PurlRequest, StatusCode, StatusResponse, - ComponentsRequest, ) from .api.components.v2.scanoss_components_pb2 import ( CompSearchRequest, @@ -104,7 +104,7 @@ class ScanossGrpc(ScanossBase): Client for gRPC functionality """ - def __init__( # noqa: PLR0913, PLR0915 + def __init__( # noqa: PLR0912, PLR0913, PLR0915 self, url: str = None, debug: bool = False, @@ -799,7 +799,9 @@ def _process_dep_file_rest(self, file, depth: int = 1) -> dict: request_id = str(uuid.uuid4()) self.print_debug(f'Sending data for Dependencies via REST (request id: {request_id})...') file_request = {'files': [file], 'depth': depth} - response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/dependencies/dependencies', request_id, file_request) + response = self.rest_post(f'{self.orig_url}{DEFAULT_URI_PREFIX}/dependencies/dependencies', + request_id, file_request + ) self.print_trace(f'Received response for Dependencies via REST (request id: {request_id}): {response}') if response: return response From 3221d1b9e33c352ffbc81bcde25a48dcdced697d Mon Sep 17 00:00:00 2001 From: eeisegn Date: Fri, 29 Aug 2025 17:56:07 +0100 Subject: [PATCH 3/4] version update --- CHANGELOG.md | 7 ++++++- src/scanoss/__init__.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62180f9a..dbf43887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Upcoming changes... +## [1.32.0] - 2025-08-29 +### Added +- Switched vulnerability and dependency APIs to use REST by default + ## [1.31.5] - 2025-08-27 ### Added - Added jira markdown option for DT @@ -655,4 +659,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.31.2]: https://github.com/scanoss/scanoss.py/compare/v1.31.1...v1.31.2 [1.31.3]: https://github.com/scanoss/scanoss.py/compare/v1.31.2...v1.31.3 [1.31.4]: https://github.com/scanoss/scanoss.py/compare/v1.31.3...v1.31.4 -[1.31.5]: https://github.com/scanoss/scanoss.py/compare/v1.31.4...v1.31.5 \ No newline at end of file +[1.31.5]: https://github.com/scanoss/scanoss.py/compare/v1.31.4...v1.31.5 +[1.31.5]: https://github.com/scanoss/scanoss.py/compare/v1.31.5...v1.32.0 diff --git a/src/scanoss/__init__.py b/src/scanoss/__init__.py index d37886ff..77eed9d1 100644 --- a/src/scanoss/__init__.py +++ b/src/scanoss/__init__.py @@ -22,4 +22,4 @@ THE SOFTWARE. """ -__version__ = '1.31.5' +__version__ = '1.32.0' From 2fc1b725b20417aee94a6b944cb83e5a7387e3be Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Fri, 29 Aug 2025 15:47:10 -0300 Subject: [PATCH 4/4] fix: Fixes scan URL --- src/scanoss/scanossapi.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/scanoss/scanossapi.py b/src/scanoss/scanossapi.py index c934c95a..25bb8516 100644 --- a/src/scanoss/scanossapi.py +++ b/src/scanoss/scanossapi.py @@ -88,7 +88,7 @@ def __init__( # noqa: PLR0913, PLR0915 """ super().__init__(debug, trace, quiet) self.url = url - self.api_key = api_key + self.api_key = api_key if api_key else SCANOSS_API_KEY self.sbom = None self.scan_format = scan_format if scan_format else 'plain' self.flags = flags @@ -107,10 +107,7 @@ def __init__( # noqa: PLR0913, PLR0915 self.headers['user-agent'] = f'scanoss-py/{__version__}' self.load_generic_headers() - self.url = url if url else SCANOSS_SCAN_URL - self.api_key = api_key if api_key else SCANOSS_API_KEY - if self.api_key and not url and not os.environ.get('SCANOSS_SCAN_URL'): - self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium + self.url = self._get_scan_url() if self.trace: logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) @@ -284,6 +281,14 @@ def load_generic_headers(self): self.api_key = value self.headers[key] = value + + def _get_scan_url(self): + url = SCANOSS_SCAN_URL + if url not in[DEFAULT_URL, DEFAULT_URL2]: + return url + if self.api_key and url in[DEFAULT_URL, DEFAULT_URL2]: + return DEFAULT_URL2 + return url # # End of ScanossApi Class #