diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index ab3b63080..b306dbd0e 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -43,10 +43,15 @@ except NameError: # Attn: these imports should NOT be at the top of the file try: from .client import CLIENT, commands + from .errors import RegistryError, ErrorCode + from epplib.models import common except ImportError: pass __all__ = [ "CLIENT", "commands", + "common", + "ErrorCode", + "RegistryError", ] diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 7ddf0a03e..156ee7608 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -67,54 +67,47 @@ class EPPLibWrapper: def _send(self, command): """Helper function used by `send`.""" try: + cmd_type = command.__class__.__name__ with self._connect as wire: response = wire.send(command) except (ValueError, ParsingError) as err: - logger.warning( - "%s failed to execute due to some syntax error." - % command.__class__.__name__, - exc_info=True, - ) - raise RegistryError() from err + message = "%s failed to execute due to some syntax error." + logger.warning(message, cmd_type, exc_info=True) + raise RegistryError(message) from err except TransportError as err: - logger.warning( - "%s failed to execute due to a connection error." - % command.__class__.__name__, - exc_info=True, - ) - raise RegistryError() from err + message = "%s failed to execute due to a connection error." + logger.warning(message, cmd_type, exc_info=True) + raise RegistryError(message) from err except LoginError as err: - logger.warning( - "%s failed to execute due to a registry login error." - % command.__class__.__name__, - exc_info=True, - ) - raise RegistryError() from err + message = "%s failed to execute due to a registry login error." + logger.warning(message, cmd_type, exc_info=True) + raise RegistryError(message) from err except Exception as err: - logger.warning( - "%s failed to execute due to an unknown error." - % command.__class__.__name__, - exc_info=True, - ) - raise RegistryError() from err + message = "%s failed to execute due to an unknown error." + logger.warning(message, cmd_type, exc_info=True) + raise RegistryError(message) from err else: if response.code >= 2000: - raise RegistryError(response.msg) + raise RegistryError(response.msg, code=response.code) else: return response - def send(self, command): + def send(self, command, *, cleaned=False): """Login, send the command, then close the connection. Tries 3 times.""" + # try to prevent use of this method without appropriate safeguards + if not cleaned: + raise ValueError("Please sanitize user input before sending it.") + counter = 0 # we'll try 3 times while True: try: return self._send(command) except RegistryError as err: - if counter == 3: # don't try again - raise err - else: + if err.should_retry() and counter < 3: counter += 1 sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms + else: # don't try again + raise err try: diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index c3028e0c4..7e45633a7 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -1,5 +1,77 @@ +from enum import IntEnum + + +class ErrorCode(IntEnum): + """ + Overview of registry response codes from RFC 5730. See RFC 5730 for full text. + + - 1000 - 1500 Success + - 2000 - 2308 Registrar did something silly + - 2400 - 2500 Registry did something silly + - 2501 - 2502 Something malicious or abusive may have occurred + """ + + COMMAND_COMPLETED_SUCCESSFULLY = 1000 + COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001 + COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300 + COMMAND_COMPLETED_SUCCESSFULLY_ACK_TO_DEQUEUE = 1301 + COMMAND_COMPLETED_SUCCESSFULLY_ENDING_SESSION = 1500 + + UNKNOWN_COMMAND = 2000 + COMMAND_SYNTAX_ERROR = 2001 + COMMAND_USE_ERROR = 2002 + REQUIRED_PARAMETER_MISSING = 2003 + PARAMETER_VALUE_RANGE_ERROR = 2004 + PARAMETER_VALUE_SYNTAX_ERROR = 2005 + UNIMPLEMENTED_PROTOCOL_VERSION = 2100 + UNIMPLEMENTED_COMMAND = 2101 + UNIMPLEMENTED_OPTION = 2102 + UNIMPLEMENTED_EXTENSION = 2103 + BILLING_FAILURE = 2104 + OBJECT_IS_NOT_ELIGIBLE_FOR_RENEWAL = 2105 + OBJECT_IS_NOT_ELIGIBLE_FOR_TRANSFER = 2106 + AUTHENTICATION_ERROR = 2200 + AUTHORIZATION_ERROR = 2201 + INVALID_AUTHORIZATION_INFORMATION = 2202 + OBJECT_PENDING_TRANSFER = 2300 + OBJECT_NOT_PENDING_TRANSFER = 2301 + OBJECT_EXISTS = 2302 + OBJECT_DOES_NOT_EXIST = 2303 + OBJECT_STATUS_PROHIBITS_OPERATION = 2304 + OBJECT_ASSOCIATION_PROHIBITS_OPERATION = 2305 + PARAMETER_VALUE_POLICY_ERROR = 2306 + UNIMPLEMENTED_OBJECT_SERVICE = 2307 + DATA_MANAGEMENT_POLICY_VIOLATION = 2308 + + COMMAND_FAILED = 2400 + COMMAND_FAILED_SERVER_CLOSING_CONNECTION = 2500 + + AUTHENTICATION_ERROR_SERVER_CLOSING_CONNECTION = 2501 + SESSION_LIMIT_EXCEEDED_SERVER_CLOSING_CONNECTION = 2502 + + class RegistryError(Exception): - pass + """ + Overview of registry response codes from RFC 5730. See RFC 5730 for full text. + + - 1000 - 1500 Success + - 2000 - 2308 Registrar did something silly + - 2400 - 2500 Registry did something silly + - 2501 - 2502 Something malicious or abusive may have occurred + """ + + def __init__(self, *args, code=None, **kwargs): + super().__init__(*args, **kwargs) + self.code = code + + def should_retry(self): + return self.code == ErrorCode.COMMAND_FAILED + + def is_server_error(self): + return self.code is not None and (self.code >= 2400 and self.code <= 2500) + + def is_client_error(self): + return self.code is not None and (self.code >= 2000 and self.code <= 2308) class LoginError(RegistryError):