]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/zeiterfassung.py
location: Remove blacklist feature
[ipfire.org.git] / src / backend / zeiterfassung.py
CommitLineData
22bbb173 1#!/usr/bin/python3
2c361abc
MT
2
3import hashlib
4import hmac
5import json
22bbb173 6import logging
2c361abc 7import tornado.httpclient
11347e46 8import urllib.parse
2c361abc 9
11347e46 10from .misc import Object
2c361abc
MT
11
12class ZeiterfassungClient(Object):
13 algorithm = "Zeiterfassung-HMAC-SHA512"
14
15 def init(self):
16 self.url = self.settings.get("zeiterfassung_url")
17
18 # API credentials
19 self.api_key = self.settings.get("zeiterfassung_api_key")
20 self.api_secret = self.settings.get("zeiterfassung_api_secret")
21
22 # Check if all configuration values are set
23 if not all((self.url, self.api_key, self.api_secret)):
24 raise RuntimeError("%s is not configured" % self.__class__.__name__)
25
22bbb173 26 def _sign_request(self, method, path, body):
2c361abc
MT
27 # Empty since we only support POST
28 canonical_query = ""
29
30 # Put everything together
31 string_to_sign = "\n".join((
32 method, path, canonical_query,
33 )).encode("utf-8") + body
34
35 # Compute HMAC
36 h = hmac.new(self.api_secret.encode("utf-8"), string_to_sign, hashlib.sha512)
37
38 return h.hexdigest()
39
22bbb173
MT
40 def _sign_response(self, body):
41 h = hmac.new(self.api_secret.encode("utf-8"), body, hashlib.sha512)
42
43 return h.hexdigest()
44
9fdf4fb7 45 async def send_request(self, path, **kwargs):
11347e46 46 url = urllib.parse.urljoin(self.url, path)
2c361abc 47
2c361abc 48 request = tornado.httpclient.HTTPRequest(url, method="POST")
22bbb173 49 request.body = urllib.parse.urlencode(kwargs)
2c361abc
MT
50
51 # Compose the signature
22bbb173 52 signature = self._sign_request("POST", path, request.body)
2c361abc
MT
53
54 # Add authorization header
55 request.headers["Authorization"] = " ".join(
56 (self.algorithm, self.api_key, signature)
57 )
58
22bbb173
MT
59 # Log request
60 logging.debug("Sending request to %s:" % request.url)
61 for header in sorted(request.headers):
62 logging.debug(" %s: %s" % (header, request.headers[header]))
f38c875a
MT
63 if request.body:
64 logging.debug("%s" % json.dumps(kwargs, indent=4, sort_keys=True))
22bbb173 65
2c361abc 66 # Send the request
9fdf4fb7 67 response = await self.backend.http_client.fetch(request)
2c361abc 68
22bbb173
MT
69 # Log response
70 logging.debug("Got response %s from %s in %.2fms:" % \
71 (response.code, response.effective_url, response.request_time * 1000))
72 for header in response.headers:
73 logging.debug(" %s: %s" % (header, response.headers[header]))
74
75 # Fetch the whole body
76 body = response.body
f38c875a
MT
77 if body:
78 # Decode the JSON response
79 body = json.loads(body)
80
81 # Log what we have received in a human-readable way
82 logging.debug("%s" % json.dumps(body, indent=4, sort_keys=True))
22bbb173
MT
83
84 # Fetch the signature
85 signature = response.headers.get("Hash")
86 if not signature:
87 raise RuntimeError("Could not find signature on response")
88
f38c875a 89 expected_signature = self._sign_response(response.body)
22bbb173
MT
90 if not hmac.compare_digest(expected_signature, signature):
91 raise RuntimeError("Invalid signature: %s" % signature)
92
f38c875a
MT
93 # Return the body
94 return body