Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions cycode/cli/commands/auth/auth_command.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import click

from cycode.cli.commands.auth.auth_manager import AuthManager
from cycode.cli.exceptions.custom_exceptions import AuthProcessError, HttpUnauthorizedError, NetworkError
from cycode.cli.exceptions.custom_exceptions import (
KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
AuthProcessError,
HttpUnauthorizedError,
RequestHttpError,
)
from cycode.cli.models import CliError, CliErrors, CliResult
from cycode.cli.printers import ConsolePrinter
from cycode.cli.sentry import add_breadcrumb, capture_exception
Expand Down Expand Up @@ -68,7 +73,7 @@ def authorization_check(context: click.Context) -> None:
)

return
except (NetworkError, HttpUnauthorizedError):
except (RequestHttpError, HttpUnauthorizedError):
ConsolePrinter(context).print_exception()

printer.print_result(failed_auth_check_res)
Expand All @@ -79,12 +84,10 @@ def _handle_exception(context: click.Context, e: Exception) -> None:
ConsolePrinter(context).print_exception()

errors: CliErrors = {
**KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
AuthProcessError: CliError(
code='auth_error', message='Authentication failed. Please try again later using the command `cycode auth`'
),
NetworkError: CliError(
code='cycode_error', message='Authentication failed. Please try again later using the command `cycode auth`'
),
}

error = errors.get(type(e))
Expand Down
49 changes: 47 additions & 2 deletions cycode/cli/exceptions/custom_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
from requests import Response

from cycode.cli.models import CliError, CliErrors


class CycodeError(Exception):
"""Base class for all custom exceptions"""


class NetworkError(CycodeError):
class RequestError(CycodeError):
...


class RequestTimeout(RequestError):
...


class RequestConnectionError(RequestError):
...


class RequestSslError(RequestConnectionError):
...


class RequestHttpError(RequestError):
def __init__(self, status_code: int, error_message: str, response: Response) -> None:
self.status_code = status_code
self.error_message = error_message
Expand All @@ -32,7 +50,7 @@ class ReportAsyncError(CycodeError):
pass


class HttpUnauthorizedError(CycodeError):
class HttpUnauthorizedError(RequestError):
def __init__(self, error_message: str, response: Response) -> None:
self.status_code = 401
self.error_message = error_message
Expand Down Expand Up @@ -68,3 +86,30 @@ def __init__(self, file_path: str) -> None:

def __str__(self) -> str:
return f'Error occurred while parsing terraform plan file. Path: {self.file_path}'


KNOWN_USER_FRIENDLY_REQUEST_ERRORS: CliErrors = {
RequestHttpError: CliError(
soft_fail=True,
code='cycode_error',
message='Cycode was unable to complete this scan. Please try again by executing the `cycode scan` command',
),
RequestTimeout: CliError(
soft_fail=True,
code='timeout_error',
message='The request timed out. Please try again by executing the `cycode scan` command',
),
HttpUnauthorizedError: CliError(
soft_fail=True,
code='auth_error',
message='Unable to authenticate to Cycode, your token is either invalid or has expired. '
'Please re-generate your token and reconfigure it by running the `cycode configure` command',
),
RequestSslError: CliError(
soft_fail=True,
code='ssl_error',
message='An SSL error occurred when trying to connect to the Cycode API. '
'If you use an on-premises installation or a proxy that intercepts SSL traffic '
'you should use the CURL_CA_BUNDLE environment variable to specify path to a valid .pem or similar.',
),
}
12 changes: 2 additions & 10 deletions cycode/cli/exceptions/handle_report_sbom_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import click

from cycode.cli.exceptions import custom_exceptions
from cycode.cli.exceptions.custom_exceptions import KNOWN_USER_FRIENDLY_REQUEST_ERRORS
from cycode.cli.models import CliError, CliErrors
from cycode.cli.printers import ConsolePrinter
from cycode.cli.sentry import capture_exception
Expand All @@ -12,11 +13,7 @@ def handle_report_exception(context: click.Context, err: Exception) -> Optional[
ConsolePrinter(context).print_exception()

errors: CliErrors = {
custom_exceptions.NetworkError: CliError(
code='cycode_error',
message='Cycode was unable to complete this report. '
'Please try again by executing the `cycode report` command',
),
**KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
custom_exceptions.ScanAsyncError: CliError(
code='report_error',
message='Cycode was unable to complete this report. '
Expand All @@ -27,11 +24,6 @@ def handle_report_exception(context: click.Context, err: Exception) -> Optional[
message='Cycode was unable to complete this report. '
'Please try again by executing the `cycode report` command',
),
custom_exceptions.HttpUnauthorizedError: CliError(
code='auth_error',
message='Unable to authenticate to Cycode, your token is either invalid or has expired. '
'Please re-generate your token and reconfigure it by running the `cycode configure` command',
),
}

if type(err) in errors:
Expand Down
14 changes: 2 additions & 12 deletions cycode/cli/exceptions/handle_scan_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import click

from cycode.cli.exceptions import custom_exceptions
from cycode.cli.exceptions.custom_exceptions import KNOWN_USER_FRIENDLY_REQUEST_ERRORS
from cycode.cli.models import CliError, CliErrors
from cycode.cli.printers import ConsolePrinter
from cycode.cli.sentry import capture_exception
Expand All @@ -17,24 +18,13 @@ def handle_scan_exception(
ConsolePrinter(context).print_exception(e)

errors: CliErrors = {
custom_exceptions.NetworkError: CliError(
soft_fail=True,
code='cycode_error',
message='Cycode was unable to complete this scan. '
'Please try again by executing the `cycode scan` command',
),
**KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
custom_exceptions.ScanAsyncError: CliError(
soft_fail=True,
code='scan_error',
message='Cycode was unable to complete this scan. '
'Please try again by executing the `cycode scan` command',
),
custom_exceptions.HttpUnauthorizedError: CliError(
soft_fail=True,
code='auth_error',
message='Unable to authenticate to Cycode, your token is either invalid or has expired. '
'Please re-generate your token and reconfigure it by running the `cycode configure` command',
),
custom_exceptions.ZipTooLargeError: CliError(
soft_fail=True,
code='zip_too_large_error',
Expand Down
4 changes: 2 additions & 2 deletions cycode/cyclient/auth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from requests import Response

from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, NetworkError
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, RequestHttpError
from cycode.cyclient import models
from cycode.cyclient.cycode_client import CycodeClient

Expand All @@ -25,7 +25,7 @@ def get_api_token(self, session_id: str, code_verifier: str) -> Optional[models.
try:
response = self.cycode_client.post(url_path=path, body=body, hide_response_content_log=True)
return self.parse_api_token_polling_response(response)
except (NetworkError, HttpUnauthorizedError) as e:
except (RequestHttpError, HttpUnauthorizedError) as e:
return self.parse_api_token_polling_response(e.response)
except Exception:
return None
Expand Down
30 changes: 19 additions & 11 deletions cycode/cyclient/cycode_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
from requests import Response, exceptions
from requests.adapters import HTTPAdapter

from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, NetworkError
from cycode.cli.exceptions.custom_exceptions import (
HttpUnauthorizedError,
RequestConnectionError,
RequestError,
RequestHttpError,
RequestSslError,
RequestTimeout,
)
from cycode.cyclient import config, logger
from cycode.cyclient.headers import get_cli_user_agent, get_correlation_id

Expand Down Expand Up @@ -110,18 +117,19 @@ def build_full_url(self, url: str, endpoint: str) -> str:

def _handle_exception(self, e: Exception) -> None:
if isinstance(e, exceptions.Timeout):
raise NetworkError(504, 'Timeout Error', e.response)

raise RequestTimeout from e
if isinstance(e, exceptions.HTTPError):
self._handle_http_exception(e)
elif isinstance(e, exceptions.ConnectionError):
raise NetworkError(502, 'Connection Error', e.response)
else:
raise e
raise self._get_http_exception(e) from e
if isinstance(e, exceptions.SSLError):
raise RequestSslError from e
if isinstance(e, exceptions.ConnectionError):
raise RequestConnectionError from e

raise e

@staticmethod
def _handle_http_exception(e: exceptions.HTTPError) -> None:
def _get_http_exception(e: exceptions.HTTPError) -> RequestError:
if e.response.status_code == 401:
raise HttpUnauthorizedError(e.response.text, e.response)
return HttpUnauthorizedError(e.response.text, e.response)

raise NetworkError(e.response.status_code, e.response.text, e.response)
return RequestHttpError(e.response.status_code, e.response.text, e.response)
2 changes: 1 addition & 1 deletion tests/cli/exceptions/test_handle_scan_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def ctx() -> click.Context:
@pytest.mark.parametrize(
'exception, expected_soft_fail',
[
(custom_exceptions.NetworkError(400, 'msg', Response()), True),
(custom_exceptions.RequestHttpError(400, 'msg', Response()), True),
(custom_exceptions.ScanAsyncError('msg'), True),
(custom_exceptions.HttpUnauthorizedError('msg', Response()), True),
(custom_exceptions.ZipTooLargeError(1000), True),
Expand Down
6 changes: 2 additions & 4 deletions tests/cyclient/test_auth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from requests import Timeout

from cycode.cli.commands.auth.auth_manager import AuthManager
from cycode.cli.exceptions.custom_exceptions import CycodeError
from cycode.cli.exceptions.custom_exceptions import CycodeError, RequestTimeout
from cycode.cyclient.auth_client import AuthClient
from cycode.cyclient.models import (
ApiTokenGenerationPollingResponse,
Expand Down Expand Up @@ -73,11 +73,9 @@ def test_start_session_timeout(client: AuthClient, start_url: str, code_challeng

responses.add(responses.POST, start_url, body=timeout_error)

with pytest.raises(CycodeError) as e_info:
with pytest.raises(RequestTimeout):
client.start_session(code_challenge)

assert e_info.value.status_code == 504


@responses.activate
def test_start_session_http_error(client: AuthClient, start_url: str, code_challenge: str) -> None:
Expand Down
17 changes: 8 additions & 9 deletions tests/cyclient/test_scan_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
from requests.exceptions import ProxyError

from cycode.cli.config import config
from cycode.cli.exceptions.custom_exceptions import CycodeError, HttpUnauthorizedError
from cycode.cli.exceptions.custom_exceptions import (
CycodeError,
HttpUnauthorizedError,
RequestConnectionError,
RequestTimeout,
)
from cycode.cli.files_collector.models.in_memory_zip import InMemoryZip
from cycode.cli.files_collector.zip_documents import zip_documents
from cycode.cli.models import Document
Expand Down Expand Up @@ -138,12 +143,9 @@ def test_zipped_file_scan_timeout_error(
responses.add(api_token_response) # mock token based client
responses.add(method=responses.POST, url=scan_url, body=timeout_error, status=504)

with pytest.raises(CycodeError) as e_info:
with pytest.raises(RequestTimeout):
scan_client.zipped_file_scan(scan_type, zip_file, scan_id=expected_scan_id, scan_parameters={})

assert e_info.value.status_code == 504
assert e_info.value.error_message == 'Timeout Error'


@pytest.mark.parametrize('scan_type', config['scans']['supported_scans'])
@responses.activate
Expand All @@ -156,8 +158,5 @@ def test_zipped_file_scan_connection_error(
responses.add(api_token_response) # mock token based client
responses.add(method=responses.POST, url=url, body=ProxyError())

with pytest.raises(CycodeError) as e_info:
with pytest.raises(RequestConnectionError):
scan_client.zipped_file_scan(scan_type, zip_file, scan_id=expected_scan_id, scan_parameters={})

assert e_info.value.status_code == 502
assert e_info.value.error_message == 'Connection Error'