-#!/usr/bin/python
+#!/usr/bin/python3
import hashlib
import hmac
import json
+import logging
import tornado.httpclient
import urllib.parse
if not all((self.url, self.api_key, self.api_secret)):
raise RuntimeError("%s is not configured" % self.__class__.__name__)
- def _make_signature(self, method, path, body):
+ def _sign_request(self, method, path, body):
# Empty since we only support POST
canonical_query = ""
return h.hexdigest()
+ def _sign_response(self, body):
+ h = hmac.new(self.api_secret.encode("utf-8"), body, hashlib.sha512)
+
+ return h.hexdigest()
+
async def send_request(self, path, **kwargs):
url = urllib.parse.urljoin(self.url, path)
- # Query arguments are all keyword arguments
- arguments = kwargs
-
request = tornado.httpclient.HTTPRequest(url, method="POST")
- request.body = urllib.parse.urlencode(arguments)
+ request.body = urllib.parse.urlencode(kwargs)
# Compose the signature
- signature = self._make_signature("POST", path, request.body)
+ signature = self._sign_request("POST", path, request.body)
# Add authorization header
request.headers["Authorization"] = " ".join(
(self.algorithm, self.api_key, signature)
)
+ # Log request
+ logging.debug("Sending request to %s:" % request.url)
+ for header in sorted(request.headers):
+ logging.debug(" %s: %s" % (header, request.headers[header]))
+
# Send the request
response = await self.backend.http_client.fetch(request)
+ # Log response
+ logging.debug("Got response %s from %s in %.2fms:" % \
+ (response.code, response.effective_url, response.request_time * 1000))
+ for header in response.headers:
+ logging.debug(" %s: %s" % (header, response.headers[header]))
+
+ # Fetch the whole body
+ body = response.body
+
+ # Fetch the signature
+ signature = response.headers.get("Hash")
+ if not signature:
+ raise RuntimeError("Could not find signature on response")
+
+ expected_signature = self._sign_response(body)
+ if not hmac.compare_digest(expected_signature, signature):
+ raise RuntimeError("Invalid signature: %s" % signature)
+
# Decode the JSON response
- return json.loads(response.body.decode())
+ return json.loads(body)