errors.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Contains exceptions and error codes."""
  16. import logging
  17. logger = logging.getLogger(__name__)
  18. class Codes(object):
  19. UNAUTHORIZED = "M_UNAUTHORIZED"
  20. FORBIDDEN = "M_FORBIDDEN"
  21. BAD_JSON = "M_BAD_JSON"
  22. NOT_JSON = "M_NOT_JSON"
  23. USER_IN_USE = "M_USER_IN_USE"
  24. ROOM_IN_USE = "M_ROOM_IN_USE"
  25. BAD_PAGINATION = "M_BAD_PAGINATION"
  26. UNKNOWN = "M_UNKNOWN"
  27. NOT_FOUND = "M_NOT_FOUND"
  28. UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
  29. LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
  30. CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
  31. CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
  32. TOO_LARGE = "M_TOO_LARGE"
  33. class CodeMessageException(Exception):
  34. """An exception with integer code and message string attributes."""
  35. def __init__(self, code, msg):
  36. logger.info("%s: %s, %s", type(self).__name__, code, msg)
  37. super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
  38. self.code = code
  39. self.msg = msg
  40. self.response_code_message = None
  41. def error_dict(self):
  42. return cs_error(self.msg)
  43. class SynapseError(CodeMessageException):
  44. """A base error which can be caught for all synapse events."""
  45. def __init__(self, code, msg, errcode=Codes.UNKNOWN):
  46. """Constructs a synapse error.
  47. Args:
  48. code (int): The integer error code (an HTTP response code)
  49. msg (str): The human-readable error message.
  50. err (str): The error code e.g 'M_FORBIDDEN'
  51. """
  52. super(SynapseError, self).__init__(code, msg)
  53. self.errcode = errcode
  54. def error_dict(self):
  55. return cs_error(
  56. self.msg,
  57. self.errcode,
  58. )
  59. class RoomError(SynapseError):
  60. """An error raised when a room event fails."""
  61. pass
  62. class RegistrationError(SynapseError):
  63. """An error raised when a registration event fails."""
  64. pass
  65. class AuthError(SynapseError):
  66. """An error raised when there was a problem authorising an event."""
  67. def __init__(self, *args, **kwargs):
  68. if "errcode" not in kwargs:
  69. kwargs["errcode"] = Codes.FORBIDDEN
  70. super(AuthError, self).__init__(*args, **kwargs)
  71. class EventStreamError(SynapseError):
  72. """An error raised when there a problem with the event stream."""
  73. def __init__(self, *args, **kwargs):
  74. if "errcode" not in kwargs:
  75. kwargs["errcode"] = Codes.BAD_PAGINATION
  76. super(EventStreamError, self).__init__(*args, **kwargs)
  77. class LoginError(SynapseError):
  78. """An error raised when there was a problem logging in."""
  79. pass
  80. class StoreError(SynapseError):
  81. """An error raised when there was a problem storing some data."""
  82. pass
  83. class InvalidCaptchaError(SynapseError):
  84. def __init__(self, code=400, msg="Invalid captcha.", error_url=None,
  85. errcode=Codes.CAPTCHA_INVALID):
  86. super(InvalidCaptchaError, self).__init__(code, msg, errcode)
  87. self.error_url = error_url
  88. def error_dict(self):
  89. return cs_error(
  90. self.msg,
  91. self.errcode,
  92. error_url=self.error_url,
  93. )
  94. class LimitExceededError(SynapseError):
  95. """A client has sent too many requests and is being throttled.
  96. """
  97. def __init__(self, code=429, msg="Too Many Requests", retry_after_ms=None,
  98. errcode=Codes.LIMIT_EXCEEDED):
  99. super(LimitExceededError, self).__init__(code, msg, errcode)
  100. self.retry_after_ms = retry_after_ms
  101. self.response_code_message = "Too Many Requests"
  102. def error_dict(self):
  103. return cs_error(
  104. self.msg,
  105. self.errcode,
  106. retry_after_ms=self.retry_after_ms,
  107. )
  108. def cs_exception(exception):
  109. if isinstance(exception, CodeMessageException):
  110. return exception.error_dict()
  111. else:
  112. logger.error("Unknown exception type: %s", type(exception))
  113. return {}
  114. def cs_error(msg, code=Codes.UNKNOWN, **kwargs):
  115. """ Utility method for constructing an error response for client-server
  116. interactions.
  117. Args:
  118. msg (str): The error message.
  119. code (int): The error code.
  120. kwargs : Additional keys to add to the response.
  121. Returns:
  122. A dict representing the error response JSON.
  123. """
  124. err = {"error": msg, "errcode": code}
  125. for key, value in kwargs.iteritems():
  126. err[key] = value
  127. return err
  128. class FederationError(RuntimeError):
  129. """ This class is used to inform remote home servers about erroneous
  130. PDUs they sent us.
  131. FATAL: The remote server could not interpret the source event.
  132. (e.g., it was missing a required field)
  133. ERROR: The remote server interpreted the event, but it failed some other
  134. check (e.g. auth)
  135. WARN: The remote server accepted the event, but believes some part of it
  136. is wrong (e.g., it referred to an invalid event)
  137. """
  138. def __init__(self, level, code, reason, affected, source=None):
  139. if level not in ["FATAL", "ERROR", "WARN"]:
  140. raise ValueError("Level is not valid: %s" % (level,))
  141. self.level = level
  142. self.code = code
  143. self.reason = reason
  144. self.affected = affected
  145. self.source = source
  146. msg = "%s %s: %s" % (level, code, reason,)
  147. super(FederationError, self).__init__(msg)
  148. def get_dict(self):
  149. return {
  150. "level": self.level,
  151. "code": self.code,
  152. "reason": self.reason,
  153. "affected": self.affected,
  154. "source": self.source if self.source else self.affected,
  155. }