#!/usr/bin/env python3 ''' API client for bocal-site ''' import json import urllib.request import hmac import hashlib from datetime import datetime, date import argparse import os.path import sys def sendReq(url): def send(payload, host): try: req = urllib.request.Request('http://{}/{}'.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) mac.update(json.dumps(payload).encode('utf-8')) auth = { 'keyId': keyId, 'timestamp': time, 'hmac': mac.hexdigest(), } return { 'auth': auth, 'req': payload, } 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()