www-bocal/api/client/apiclient.py

154 lines
4.1 KiB
Python

#!/usr/bin/env python3
""" API client for bocal-site """
import argparse
import hashlib
import hmac
import json
import os.path
import sys
import urllib.request
from datetime import date, datetime
def sendReq(url):
def send(payload, host):
try:
req = urllib.request.Request(
"https://{}/{}".format(host, url), json.dumps(payload).encode("ascii")
)
req.add_header("Content-Type", "application/json")
handle = urllib.request.urlopen(req)
code = handle.getcode()
content = handle.read()
handle.close()
return (code, content.decode("utf-8"))
except urllib.error.HTTPError as e:
return (e.code, e.read().decode("utf-8"))
def authentify(apiKey, payload):
keyId, key = apiKey.split("$")
keyId = int(keyId)
time = datetime.now().timestamp()
mac = hmac.new(
key.encode("utf-8"),
msg=str(int(time)).encode("utf-8"),
digestmod=hashlib.sha256,
)
payload_enc = json.dumps(payload)
mac.update(payload_enc.encode("utf-8"))
auth = {
"keyId": keyId,
"timestamp": time,
"hmac": mac.hexdigest(),
}
return {
"auth": auth,
"req": payload_enc,
}
def decorator(fct):
"""Decorator. Adds authentication layer."""
def wrap(host, apiKey, *args, **kwargs):
innerReq = fct(*args, **kwargs)
payload = authentify(apiKey, innerReq)
return send(payload, host)
return wrap
return decorator
@sendReq(url="api/publish")
def publish(bocId, url, date):
return {
"id": bocId,
"url": url,
"date": date.strftime("%Y-%m-%d"),
}
###############################################################################
TOKEN_DFT_FILE = os.path.expanduser("~/.bocal_api_token")
DFT_HOST = "bocal.cof.ens.fr"
def read_token(path):
token = ""
try:
with open(path, "r") as handle:
token = handle.readline().strip()
except FileNotFoundError:
print(
"[Erreur] Fichier d'identifiants absent (`{}`).".format(path),
file=sys.stderr,
)
sys.exit(1)
return token
def cmd(func):
def wrap(parse_args, *args, **kwargs):
token = read_token(parse_args.creds)
return func(token, parse_args, *args, **kwargs)
return wrap
@cmd
def cmd_publish(token, args):
if not args.date:
publish_date = date.today()
else:
year, month, day = [int(x) for x in args.date.strip().split("-")]
publish_date = date(year=year, month=month, day=day)
(ret_code, ret_str) = publish(args.host, token, args.numero, args.url, publish_date)
if ret_code == 200:
print("Succès :)")
else:
print("[Erreur :c] {} : {}".format(ret_code, ret_str))
sys.exit(1)
def setup_argparse():
parser = argparse.ArgumentParser()
parser.add_argument(
"--host",
help=("Adresse du site à contacter (par défaut, " "`{}`).".format(DFT_HOST)),
)
parser.add_argument(
"--creds",
help=(
"Fichier contenant le token API à utiliser "
"(par défaut, `{}`)".format(TOKEN_DFT_FILE)
),
)
parser.set_defaults(creds=TOKEN_DFT_FILE, host=DFT_HOST)
subparsers = parser.add_subparsers()
parser_publish = subparsers.add_parser("publier", help="Publier un numéro du BOcal")
parser_publish.add_argument("numero", help="Numéro du BOcal")
parser_publish.add_argument("url", help="Adresse (locale) du PDF du BOcal")
parser_publish.add_argument("-d", "--date", help="Date de publication indiquée")
parser_publish.set_defaults(func=cmd_publish)
out_args = parser.parse_args()
if "func" not in out_args: # No subcommand provided
print("You must provide a command.", file=sys.stderr)
print(parser.parse_args(["-h"]), file=sys.stderr)
sys.exit(1)
return out_args
def main():
args = setup_argparse()
args.func(args)
if __name__ == "__main__":
main()