]> git.ipfire.org Git - ipfire.org.git/commitdiff
zeiterfassung: Validate API responses
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 4 Feb 2020 12:27:50 +0000 (12:27 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 4 Feb 2020 12:27:50 +0000 (12:27 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/backend/zeiterfassung.py

index fef5e00b1fa85abd0bf00ab549a59f84dad30d0b..c999a0ca093ee36f9b7d9c6e23c5b29e72d75322 100644 (file)
@@ -1,8 +1,9 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import hashlib
 import hmac
 import json
+import logging
 import tornado.httpclient
 import urllib.parse
 
@@ -22,7 +23,7 @@ class ZeiterfassungClient(Object):
                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 = ""
 
@@ -36,25 +37,50 @@ class ZeiterfassungClient(Object):
 
                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)