#!/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()