diff --git a/api/client/apiclient.py b/api/client/apiclient.py index 018a430..2d01d7c 100644 --- a/api/client/apiclient.py +++ b/api/client/apiclient.py @@ -1,16 +1,21 @@ +#!/usr/bin/env python3 + ''' API client for bocal-site ''' import json import urllib.request import hmac import hashlib -from datetime import datetime +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), + 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) @@ -28,7 +33,8 @@ def sendReq(url): 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')) + payload_enc = json.dumps(payload) + mac.update(payload_enc.encode('utf-8')) auth = { 'keyId': keyId, @@ -38,7 +44,7 @@ def sendReq(url): return { 'auth': auth, - 'req': payload, + 'req': payload_enc, } def decorator(fct): @@ -59,3 +65,85 @@ def publish(bocId, url, date): '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() diff --git a/api/models.py b/api/models.py index 2ea06c5..1ce36ca 100644 --- a/api/models.py +++ b/api/models.py @@ -5,6 +5,7 @@ import hashlib import random import string + class ApiKey(models.Model): ''' An API key, to login using the API @@ -32,7 +33,8 @@ class ApiKey(models.Model): if not self.key: KEY_SIZE = 64 KEY_CHARS = string.ascii_letters + string.digits - self.key = ''.join(random.choices(KEY_CHARS, k=KEY_SIZE)) + self.key = ''.join( + [random.choice(KEY_CHARS) for _ in range(KEY_SIZE)]) @property def keyId(self): diff --git a/api/views.py b/api/views.py index 200ff06..f015373 100644 --- a/api/views.py +++ b/api/views.py @@ -20,8 +20,7 @@ def authentify(data, payload): except models.ApiKey.DoesNotExist: return response.HttpResponseForbidden('Bad authentication') - normPayload = json.dumps(payload) - if not key.isCorrect(data['timestamp'], data['hmac'], normPayload): + if not key.isCorrect(data['timestamp'], data['hmac'], payload): return response.HttpResponseForbidden('Bad authentication') @@ -30,22 +29,31 @@ def apiView(required=[]): @csrf_exempt def wrap(request, *args, **kwargs): try: - data = json.loads(request.body) - except json.decoder.JSONDecoreError: + data = json.loads(request.body.decode('utf-8')) + except TypeError: + return response.HttpResponseBadRequest("Bad packet format") + except json.decoder.JSONDecodeError: return response.HttpResponseBadRequest("Bad json") try: authData = data['auth'] - reqData = data['req'] + reqDataOrig = data['req'] except KeyError: return response.HttpResponseBadRequest("Bad request format") + try: + reqData = json.loads(reqDataOrig) + except TypeError: + return response.HttpResponseBadRequest("Bad packet format") + except json.decoder.JSONDecodeError: + return response.HttpResponseBadRequest("Bad inner json") + for field in required: if field not in reqData: return response.HttpResponseBadRequest( "Missing field {}".format(field)) - authVal = authentify(authData, reqData) + authVal = authentify(authData, reqDataOrig) if authVal is not None: return authVal diff --git a/bocal/.gitignore b/bocal/.gitignore new file mode 100644 index 0000000..fce19e4 --- /dev/null +++ b/bocal/.gitignore @@ -0,0 +1 @@ +settings.py diff --git a/bocal/settings.py b/bocal/settings.py deleted file mode 120000 index f1c999f..0000000 --- a/bocal/settings.py +++ /dev/null @@ -1 +0,0 @@ -settings_dev.py \ No newline at end of file diff --git a/bocal_auth/cas_backend.py b/bocal_auth/cas_backend.py index ed50d2b..af8e0b8 100644 --- a/bocal_auth/cas_backend.py +++ b/bocal_auth/cas_backend.py @@ -1,6 +1,5 @@ from django_cas_ng.backends import CASBackend from .models import CasUser -from . import rhosts class BOcalCASBackend(CASBackend): @@ -11,3 +10,4 @@ class BOcalCASBackend(CASBackend): def configure_user(self, user): casUser = CasUser(user=user) casUser.save() + return user diff --git a/bocal_auth/rhosts.py b/bocal_auth/rhosts.py index 2a17323..1b9230e 100644 --- a/bocal_auth/rhosts.py +++ b/bocal_auth/rhosts.py @@ -71,9 +71,11 @@ def grantBOcalPrivileges(user): def requireCasUser(fct): + def hasCas(user): + return CasUser.objects.filter(user=user).count() > 0 + def wrap(user, *args, **kwargs): - qs = CasUser.objects.filter(user=user) - if not qs.count() > 0: + if not hasCas(user): return return fct(user, *args, **kwargs) return wrap diff --git a/mainsite/migrations/0008_publication_unknown_date.py b/mainsite/migrations/0008_publication_unknown_date.py new file mode 100644 index 0000000..810d600 --- /dev/null +++ b/mainsite/migrations/0008_publication_unknown_date.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-17 14:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mainsite', '0007_siteconfiguration_specialpublisdescr'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='unknown_date', + field=models.BooleanField(default=False, help_text="La date de publication du BOcal est inconnue parce qu'il est trop vieux. La date indiquée ne servira qu'à le ranger dans une année et à ordonner les BOcals.", verbose_name='Date inconnue'), + ), + ] diff --git a/mainsite/models.py b/mainsite/models.py index 522d786..76fba29 100644 --- a/mainsite/models.py +++ b/mainsite/models.py @@ -32,6 +32,13 @@ class Publication(models.Model): # ^ This is not a URLField because we need internal URLS, eg `/static/blah` date = DateField('Publication') + unknown_date = BooleanField('Date inconnue', + help_text=("La date de publication du BOcal " + "est inconnue parce qu'il est " + "trop vieux. La date indiquée ne " + "servira qu'à le ranger dans une " + "année et à ordonner les BOcals."), + default=False) is_special = BooleanField('Numéro spécial', help_text='Numéro du BOcal non-numéroté', default=False) diff --git a/mainsite/templates/mainsite/publications_list_view.html b/mainsite/templates/mainsite/publications_list_view.html index f2b2ee3..3f7f574 100644 --- a/mainsite/templates/mainsite/publications_list_view.html +++ b/mainsite/templates/mainsite/publications_list_view.html @@ -16,7 +16,11 @@ Millésime {{ year_range }} {% for bocal in publications %} + {% if bocal.unknown_date %} + + {% else %} + {% endif %}
Date inconnue{{ bocal.date | date:"d/m/Y" }} {{ bocal }}