Skip to content

Commit

Permalink
Removing non-transient errors in favour of generic EvervaultError (#128)
Browse files Browse the repository at this point in the history
* Removing non-transient errors in favour of generic EvervaultError

* Removing duplication from tests

* Linting

* Linting
  • Loading branch information
DeirdreCleary committed Oct 27, 2023
1 parent 3d0955d commit c37a3f7
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 405 deletions.
7 changes: 7 additions & 0 deletions .changeset/four-pans-ring.md
@@ -0,0 +1,7 @@
---
"evervault-python": major
---

Simplifying errors thrown by the SDK.

Previously we exposed many different error types for users to handle, but in most cases these errors were not something that could be caught and handled, but rather indicative of a larger configuration issue. This change simplifies the errors thrown by grouping them into the generic EvervaultError unless they are a transient error which can be handled programmatically.
8 changes: 4 additions & 4 deletions evervault/__init__.py
@@ -1,6 +1,6 @@
"""Package for the evervault SDK"""
from .client import Client
from .errors.evervault_errors import AuthenticationError, UnsupportedCurveError
from .errors.evervault_errors import EvervaultError
from .cages_v2 import CageRequestsSessionBeta, CageRequestsSession
import os
import sys
Expand Down Expand Up @@ -129,15 +129,15 @@ def _warn_if_python_version_unsupported_for_async():

def __client():
if not _app_uuid:
raise AuthenticationError(
raise EvervaultError(
"Your App's App UUID must be entered using evervault.init('<APP-ID>', '<API-KEY>')"
)
if not _api_key:
raise AuthenticationError(
raise EvervaultError(
"Your App's API Key must be entered using evervault.init('<APP-ID>', '<API-KEY>')"
)
if _curve not in SUPPORTED_CURVES:
raise UnsupportedCurveError(f"The {_curve} curve is not supported.")
raise EvervaultError(f"The {_curve} curve is not supported.")
global ev_client
if not ev_client:
max_file_size_in_mb = int(
Expand Down
6 changes: 3 additions & 3 deletions evervault/cages_v2.py
Expand Up @@ -2,7 +2,7 @@
import urllib3
import evervault_attestation_bindings
from types import MethodType
from evervault.errors.evervault_errors import CertDownloadError
from evervault.errors.evervault_errors import EvervaultError
import tempfile
import base64

Expand Down Expand Up @@ -152,7 +152,7 @@ def __init__(self, cage_attestation_data, cage_ca_host, cages_host):
pass

if ca_content is None:
raise CertDownloadError(
raise EvervaultError(
f"Unable to install the Evervault Cages CA cert from {self.ca_host}. "
)

Expand All @@ -164,7 +164,7 @@ def __init__(self, cage_attestation_data, cage_ca_host, cages_host):
cert_file.write(ca_content)
self.cert_path = cert_file.name
except:
raise CertDownloadError(
raise EvervaultError(
f"Unable to install the Evervault Cages CA cert from {self.ca_host}. "
"Likely a permissions error when attempting to write to the /tmp/ directory."
)
Expand Down
10 changes: 5 additions & 5 deletions evervault/client.py
Expand Up @@ -10,7 +10,7 @@
from .http.request import Request
from .crypto.client import Client as CryptoClient
from .services.timeservice import TimeService
from .errors.evervault_errors import UndefinedDataError, DecryptionError
from .errors.evervault_errors import EvervaultError


class Client(object):
Expand Down Expand Up @@ -48,9 +48,9 @@ def encrypt(self, data):

def decrypt(self, data):
if data is None:
raise UndefinedDataError("Data is not defined")
raise EvervaultError("Data is not defined")
elif not isinstance(data, (str, dict, list, bytes)):
raise DecryptionError(
raise EvervaultError(
"data must be of type `str`, `dict`, `list` or `bytes`"
)
headers = self.__build_decrypt_headers(type(data))
Expand All @@ -69,11 +69,11 @@ def decrypt(self, data):

def create_token(self, action, payload, expiry=None):
if payload is None:
raise UndefinedDataError(
raise EvervaultError(
"Payload must be defined. It ensures that the generated token will only be able to be used to decrypt this specific payload"
)
if expiry and not isinstance(expiry, datetime):
raise UndefinedDataError("expiry must be an instance of `datetime`")
raise EvervaultError("expiry must be an instance of `datetime`")
if expiry and isinstance(expiry, datetime):
expiry = int(expiry.timestamp() * 1000)
data = {
Expand Down
15 changes: 6 additions & 9 deletions evervault/crypto/client.py
@@ -1,8 +1,5 @@
from ..errors.evervault_errors import (
UndefinedDataError,
InvalidPublicKeyError,
MissingTeamEcdhKey,
UnknownEncryptType,
EvervaultError,
ExceededMaxFileSizeError,
)
from ..datatypes.map import map_header_type
Expand Down Expand Up @@ -45,12 +42,12 @@ def __init__(self, api_key=None, curve="SECP256K1", max_file_size_in_mb=25):

def encrypt_data(self, fetch, data):
if data is None:
raise UndefinedDataError("Data not defined")
raise EvervaultError("Data not defined")
self.__fetch_cage_key(fetch)
self.shared_key = self.__derive_shared_key()

if self.shared_key is None or type(self.shared_key) != bytes:
raise InvalidPublicKeyError("Provided EC compressed point is invalid")
raise EvervaultError("Retrieved key is invalid")

if type(data) == bytes:
return self.__encrypt_file(data)
Expand All @@ -61,7 +58,7 @@ def encrypt_data(self, fetch, data):
elif self.__encryptable_data(data):
return self.__encrypt_string(data)
else:
raise UnknownEncryptType(f"Cannot encrypt unsupported type {data}")
raise EvervaultError(f"Cannot encrypt unsupported type {data}")

def __traverse_and_encrypt(self, data):
if type(data) == list:
Expand All @@ -79,7 +76,7 @@ def __traverse_and_encrypt(self, data):
elif self.__encryptable_data(data):
return self.__encrypt_string(data)
else:
raise UnknownEncryptType(f"Cannot encrypt unsupported type {data}")
raise EvervaultError(f"Cannot encrypt unsupported type {data}")

def __encrypt_object(self, data):
encrypted_data = {}
Expand Down Expand Up @@ -195,7 +192,7 @@ def __fetch_cage_key(self, fetch):

def __derive_shared_key(self):
if self.team_ecdh_key is None:
raise MissingTeamEcdhKey("Team ECDH key not set in client")
raise EvervaultError("Team ECDH key not set in client")
elif self.shared_key is None:
return self.__generate_shared_key()
else:
Expand Down
34 changes: 13 additions & 21 deletions evervault/errors/error_handler.py
Expand Up @@ -8,8 +8,8 @@ def raise_errors_on_function_run_failure(function_body):
stack = error.get("stack")
id = function_body.get("id")
raise errors.FunctionRuntimeError(message, stack, id)
raise errors.UnexpectedError(
"An unexpected error occurred. Please contact Evervault support"
raise errors.EvervaultError(
"An unexpected error occurred running your Function. Please contact Evervault support"
)


Expand All @@ -19,50 +19,42 @@ def raise_errors_on_api_error(resp, response_body):
code = response_body.get("code")
detail = response_body.get("detail")

if code == "unauthorized":
raise errors.AuthenticationError(detail)
if code == "forbidden":
raise errors.ForbiddenError(detail)
if code == "unprocessable-content":
raise errors.DecryptionError(detail)
if code == "functions/request-timeout":
raise errors.FunctionTimeoutError(detail)
if code == "functions/function-not-ready":
raise errors.FunctionNotReadyError(detail)
if code == "functions/forbidden-ip":
raise errors.ForbiddenIPError(detail)
raise errors.EvervaultError(detail)


def raise_error_using_status_code(resp, body=None):
if resp.status_code < 400:
return
if resp.status_code == 404:
raise errors.ResourceNotFound("Resource Not Found")
raise errors.EvervaultError("Resource not found")
elif resp.status_code == 400:
raise errors.BadRequestError("Bad request")
raise errors.EvervaultError("Bad request")
elif resp.status_code == 401:
raise errors.AuthenticationError("Unauthorized")
raise errors.EvervaultError("Unauthorized")
elif resp.status_code == 403:
if (
"x-evervault-error-code" in resp.headers
and resp.headers["x-evervault-error-code"] == "forbidden-ip-error"
):
raise errors.ForbiddenIPError("IP is not present in Cage whitelist")
raise errors.EvervaultError("IP is not present in Function whitelist")
else:
raise errors.AuthenticationError("Forbidden")
raise errors.EvervaultError("Forbidden")
elif resp.status_code == 408:
raise errors.TimeoutError("Request timed out")
raise errors.FunctionRuntimeError("Request timed out")
elif resp.status_code == 422:
raise errors.DecryptionError("Unable to decrypt data")
raise errors.EvervaultError("Unable to decrypt data")
elif resp.status_code == 500:
raise errors.ServerError("Server Error")
raise errors.EvervaultError("Server Error")
elif resp.status_code == 502:
raise errors.BadGatewayError("Bad Gateway Error")
raise errors.EvervaultError("Bad Gateway Error")
elif resp.status_code == 503:
raise errors.ServiceUnavailableError("Service Unavailable")
raise errors.EvervaultError("Service Unavailable")
else:
raise errors.UnexpectedError(__message_for_unexpected_error_without_type(body))
raise errors.EvervaultError(__message_for_unexpected_error_without_type(body))


def __message_for_unexpected_error_without_type(error_details):
Expand Down
76 changes: 0 additions & 76 deletions evervault/errors/evervault_errors.py
Expand Up @@ -5,86 +5,10 @@ def __init__(self, message=None, context=None):
self.context = context


class ArgumentError(ValueError, EvervaultError):
pass


class HttpError(EvervaultError):
pass


class ResourceNotFound(EvervaultError):
pass


class AuthenticationError(EvervaultError):
pass


class TimeoutError(EvervaultError):
pass


class DecryptionError(EvervaultError):
pass


class ServerError(EvervaultError):
pass


class BadGatewayError(EvervaultError):
pass


class ServiceUnavailableError(EvervaultError):
pass


class BadRequestError(EvervaultError):
pass


class UndefinedDataError(EvervaultError):
pass


class InvalidPublicKeyError(EvervaultError):
pass


class UnexpectedError(EvervaultError):
pass


class MissingTeamEcdhKey(EvervaultError):
pass


class UnknownEncryptType(EvervaultError):
pass


class CertDownloadError(EvervaultError):
pass


class UnsupportedCurveError(EvervaultError):
pass


class ExceededMaxFileSizeError(EvervaultError):
pass


class ForbiddenError(EvervaultError):
pass


class ForbiddenIPError(EvervaultError):
pass


class FunctionTimeoutError(EvervaultError):
pass

Expand Down
6 changes: 3 additions & 3 deletions evervault/http/requestintercept.py
Expand Up @@ -6,7 +6,7 @@
import certifi
import tempfile
import ssl
from evervault.errors.evervault_errors import CertDownloadError
from evervault.errors.evervault_errors import EvervaultError
from evervault.http.outboundrelayconfig import RelayOutboundConfig

EVERVAULT_DOMAINS = ["evervault.com", "evervault.io", "evervault.test"]
Expand Down Expand Up @@ -212,7 +212,7 @@ def __get_cert(self):
pass

if ca_content is None:
raise CertDownloadError(
raise EvervaultError(
f"Unable to install the Evervault root certificate from {self.ca_host}. "
)

Expand All @@ -223,7 +223,7 @@ def __get_cert(self):
cert_file.write(bytes(certifi.contents(), "ascii") + ca_content)
self.cert_path = cert_file.name
except:
raise CertDownloadError(
raise EvervaultError(
f"Unable to install the Evervault root certficate from {self.ca_host}. "
"Likely a permissions error when attempting to write to the /tmp/ directory."
)
Expand Down

0 comments on commit c37a3f7

Please sign in to comment.