OpenStack Identity Authentication Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

461 lines
12 KiB

  1. # Copyright 2010 Jacob Kaplan-Moss
  2. # Copyright 2011 Nebula, Inc.
  3. # Copyright 2013 Alessio Ababilov
  4. # Copyright 2013 OpenStack Foundation
  5. # All Rights Reserved.
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  8. # not use this file except in compliance with the License. You may obtain
  9. # a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  16. # License for the specific language governing permissions and limitations
  17. # under the License.
  18. """HTTP Exceptions used by keystoneauth1."""
  19. import inspect
  20. import sys
  21. from keystoneauth1.exceptions import auth
  22. from keystoneauth1.exceptions import base
  23. __all__ = ('HttpError',
  24. 'HTTPClientError',
  25. 'BadRequest',
  26. 'Unauthorized',
  27. 'PaymentRequired',
  28. 'Forbidden',
  29. 'NotFound',
  30. 'MethodNotAllowed',
  31. 'NotAcceptable',
  32. 'ProxyAuthenticationRequired',
  33. 'RequestTimeout',
  34. 'Conflict',
  35. 'Gone',
  36. 'LengthRequired',
  37. 'PreconditionFailed',
  38. 'RequestEntityTooLarge',
  39. 'RequestUriTooLong',
  40. 'UnsupportedMediaType',
  41. 'RequestedRangeNotSatisfiable',
  42. 'ExpectationFailed',
  43. 'UnprocessableEntity',
  44. 'HttpServerError',
  45. 'InternalServerError',
  46. 'HttpNotImplemented',
  47. 'BadGateway',
  48. 'ServiceUnavailable',
  49. 'GatewayTimeout',
  50. 'HttpVersionNotSupported',
  51. 'from_response')
  52. class HttpError(base.ClientException):
  53. """The base exception class for all HTTP exceptions."""
  54. http_status = 0
  55. message = "HTTP Error"
  56. def __init__(self, message=None, details=None,
  57. response=None, request_id=None,
  58. url=None, method=None, http_status=None,
  59. retry_after=0):
  60. self.http_status = http_status or self.http_status
  61. self.message = message or self.message
  62. self.details = details
  63. self.request_id = request_id
  64. self.response = response
  65. self.url = url
  66. self.method = method
  67. formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
  68. self.retry_after = retry_after
  69. if request_id:
  70. formatted_string += " (Request-ID: %s)" % request_id
  71. super(HttpError, self).__init__(formatted_string)
  72. class HTTPClientError(HttpError):
  73. """Client-side HTTP error.
  74. Exception for cases in which the client seems to have erred.
  75. """
  76. message = "HTTP Client Error"
  77. class HttpServerError(HttpError):
  78. """Server-side HTTP error.
  79. Exception for cases in which the server is aware that it has
  80. erred or is incapable of performing the request.
  81. """
  82. message = "HTTP Server Error"
  83. class BadRequest(HTTPClientError):
  84. """HTTP 400 - Bad Request.
  85. The request cannot be fulfilled due to bad syntax.
  86. """
  87. http_status = 400
  88. message = "Bad Request"
  89. class Unauthorized(HTTPClientError):
  90. """HTTP 401 - Unauthorized.
  91. Similar to 403 Forbidden, but specifically for use when authentication
  92. is required and has failed or has not yet been provided.
  93. """
  94. http_status = 401
  95. message = "Unauthorized"
  96. class PaymentRequired(HTTPClientError):
  97. """HTTP 402 - Payment Required.
  98. Reserved for future use.
  99. """
  100. http_status = 402
  101. message = "Payment Required"
  102. class Forbidden(HTTPClientError):
  103. """HTTP 403 - Forbidden.
  104. The request was a valid request, but the server is refusing to respond
  105. to it.
  106. """
  107. http_status = 403
  108. message = "Forbidden"
  109. class NotFound(HTTPClientError):
  110. """HTTP 404 - Not Found.
  111. The requested resource could not be found but may be available again
  112. in the future.
  113. """
  114. http_status = 404
  115. message = "Not Found"
  116. class MethodNotAllowed(HTTPClientError):
  117. """HTTP 405 - Method Not Allowed.
  118. A request was made of a resource using a request method not supported
  119. by that resource.
  120. """
  121. http_status = 405
  122. message = "Method Not Allowed"
  123. class NotAcceptable(HTTPClientError):
  124. """HTTP 406 - Not Acceptable.
  125. The requested resource is only capable of generating content not
  126. acceptable according to the Accept headers sent in the request.
  127. """
  128. http_status = 406
  129. message = "Not Acceptable"
  130. class ProxyAuthenticationRequired(HTTPClientError):
  131. """HTTP 407 - Proxy Authentication Required.
  132. The client must first authenticate itself with the proxy.
  133. """
  134. http_status = 407
  135. message = "Proxy Authentication Required"
  136. class RequestTimeout(HTTPClientError):
  137. """HTTP 408 - Request Timeout.
  138. The server timed out waiting for the request.
  139. """
  140. http_status = 408
  141. message = "Request Timeout"
  142. class Conflict(HTTPClientError):
  143. """HTTP 409 - Conflict.
  144. Indicates that the request could not be processed because of conflict
  145. in the request, such as an edit conflict.
  146. """
  147. http_status = 409
  148. message = "Conflict"
  149. class Gone(HTTPClientError):
  150. """HTTP 410 - Gone.
  151. Indicates that the resource requested is no longer available and will
  152. not be available again.
  153. """
  154. http_status = 410
  155. message = "Gone"
  156. class LengthRequired(HTTPClientError):
  157. """HTTP 411 - Length Required.
  158. The request did not specify the length of its content, which is
  159. required by the requested resource.
  160. """
  161. http_status = 411
  162. message = "Length Required"
  163. class PreconditionFailed(HTTPClientError):
  164. """HTTP 412 - Precondition Failed.
  165. The server does not meet one of the preconditions that the requester
  166. put on the request.
  167. """
  168. http_status = 412
  169. message = "Precondition Failed"
  170. class RequestEntityTooLarge(HTTPClientError):
  171. """HTTP 413 - Request Entity Too Large.
  172. The request is larger than the server is willing or able to process.
  173. """
  174. http_status = 413
  175. message = "Request Entity Too Large"
  176. def __init__(self, *args, **kwargs):
  177. try:
  178. self.retry_after = int(kwargs.pop('retry_after'))
  179. except (KeyError, ValueError):
  180. self.retry_after = 0
  181. super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
  182. class RequestUriTooLong(HTTPClientError):
  183. """HTTP 414 - Request-URI Too Long.
  184. The URI provided was too long for the server to process.
  185. """
  186. http_status = 414
  187. message = "Request-URI Too Long"
  188. class UnsupportedMediaType(HTTPClientError):
  189. """HTTP 415 - Unsupported Media Type.
  190. The request entity has a media type which the server or resource does
  191. not support.
  192. """
  193. http_status = 415
  194. message = "Unsupported Media Type"
  195. class RequestedRangeNotSatisfiable(HTTPClientError):
  196. """HTTP 416 - Requested Range Not Satisfiable.
  197. The client has asked for a portion of the file, but the server cannot
  198. supply that portion.
  199. """
  200. http_status = 416
  201. message = "Requested Range Not Satisfiable"
  202. class ExpectationFailed(HTTPClientError):
  203. """HTTP 417 - Expectation Failed.
  204. The server cannot meet the requirements of the Expect request-header field.
  205. """
  206. http_status = 417
  207. message = "Expectation Failed"
  208. class UnprocessableEntity(HTTPClientError):
  209. """HTTP 422 - Unprocessable Entity.
  210. The request was well-formed but was unable to be followed due to semantic
  211. errors.
  212. """
  213. http_status = 422
  214. message = "Unprocessable Entity"
  215. class InternalServerError(HttpServerError):
  216. """HTTP 500 - Internal Server Error.
  217. A generic error message, given when no more specific message is suitable.
  218. """
  219. http_status = 500
  220. message = "Internal Server Error"
  221. # NotImplemented is a python keyword.
  222. class HttpNotImplemented(HttpServerError):
  223. """HTTP 501 - Not Implemented.
  224. The server either does not recognize the request method, or it lacks
  225. the ability to fulfill the request.
  226. """
  227. http_status = 501
  228. message = "Not Implemented"
  229. class BadGateway(HttpServerError):
  230. """HTTP 502 - Bad Gateway.
  231. The server was acting as a gateway or proxy and received an invalid
  232. response from the upstream server.
  233. """
  234. http_status = 502
  235. message = "Bad Gateway"
  236. class ServiceUnavailable(HttpServerError):
  237. """HTTP 503 - Service Unavailable.
  238. The server is currently unavailable.
  239. """
  240. http_status = 503
  241. message = "Service Unavailable"
  242. class GatewayTimeout(HttpServerError):
  243. """HTTP 504 - Gateway Timeout.
  244. The server was acting as a gateway or proxy and did not receive a timely
  245. response from the upstream server.
  246. """
  247. http_status = 504
  248. message = "Gateway Timeout"
  249. class HttpVersionNotSupported(HttpServerError):
  250. """HTTP 505 - HttpVersion Not Supported.
  251. The server does not support the HTTP protocol version used in the request.
  252. """
  253. http_status = 505
  254. message = "HTTP Version Not Supported"
  255. # _code_map contains all the classes that have http_status attribute.
  256. _code_map = dict(
  257. (getattr(obj, 'http_status', None), obj)
  258. for name, obj in vars(sys.modules[__name__]).items()
  259. if inspect.isclass(obj) and getattr(obj, 'http_status', False)
  260. )
  261. def from_response(response, method, url):
  262. """Return an instance of :class:`HttpError` or subclass based on response.
  263. :param response: instance of `requests.Response` class
  264. :param method: HTTP method used for request
  265. :param url: URL used for request
  266. """
  267. req_id = response.headers.get("x-openstack-request-id")
  268. kwargs = {
  269. "http_status": response.status_code,
  270. "response": response,
  271. "method": method,
  272. "url": url,
  273. "request_id": req_id,
  274. }
  275. if "retry-after" in response.headers:
  276. kwargs["retry_after"] = response.headers["retry-after"]
  277. content_type = response.headers.get("Content-Type", "")
  278. if content_type.startswith("application/json"):
  279. try:
  280. body = response.json()
  281. except ValueError:
  282. pass
  283. else:
  284. if isinstance(body, dict) and isinstance(body.get("error"), dict):
  285. error = body["error"]
  286. kwargs["message"] = error.get("message")
  287. kwargs["details"] = error.get("details")
  288. elif (isinstance(body, dict) and
  289. isinstance(body.get("errors"), list)):
  290. # if the error response follows the API SIG guidelines, it
  291. # will return a list of errors. in this case, only the first
  292. # error is shown, but if there are multiple the user will be
  293. # alerted to that fact.
  294. errors = body["errors"]
  295. if len(errors) == 0:
  296. # just in case we get an empty array
  297. kwargs["message"] = None
  298. kwargs["details"] = None
  299. else:
  300. if len(errors) > 1:
  301. # if there is more than one error, let the user know
  302. # that multiple were seen.
  303. msg_hdr = ("Multiple error responses, "
  304. "showing first only: ")
  305. else:
  306. msg_hdr = ""
  307. kwargs["message"] = "{}{}".format(msg_hdr,
  308. errors[0].get("title"))
  309. kwargs["details"] = errors[0].get("detail")
  310. else:
  311. kwargs["message"] = "Unrecognized schema in response body."
  312. elif content_type.startswith("text/"):
  313. kwargs["details"] = response.text
  314. # we check explicity for 401 in case of auth receipts
  315. if (response.status_code == 401
  316. and "Openstack-Auth-Receipt" in response.headers):
  317. return auth.MissingAuthMethods(response)
  318. try:
  319. cls = _code_map[response.status_code]
  320. except KeyError:
  321. if 500 <= response.status_code < 600:
  322. cls = HttpServerError
  323. elif 400 <= response.status_code < 500:
  324. cls = HTTPClientError
  325. else:
  326. cls = HttpError
  327. return cls(**kwargs)