Add API publish view [UNTESTED]
This commit is contained in:
parent
481775a040
commit
25102a8233
3 changed files with 115 additions and 3 deletions
|
@ -1,3 +1,41 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class ApiKey(models.Model):
|
||||||
|
''' An API key, to login using the API
|
||||||
|
|
||||||
|
An API key consists in a somewhat long chunk of ascii text, *not*
|
||||||
|
containing any dollar ($) sign. It is saved on the client's machine as
|
||||||
|
a string "keyId$key".
|
||||||
|
An API token (to authentify a request) is a triplet (ts, kid, hmac) of
|
||||||
|
a timestamp `ts`, the key id `kid` and hmac = `HMAC(key, ts, sha256)`.
|
||||||
|
'''
|
||||||
|
keyId = models.IntegerField("API key id",
|
||||||
|
primary_key=True)
|
||||||
|
key = models.CharField("API key",
|
||||||
|
max_length=128)
|
||||||
|
name = models.CharField("Key name",
|
||||||
|
max_length=256,
|
||||||
|
help_text="Where is this key used from?")
|
||||||
|
last_used = models.DateTimeField("Last used",
|
||||||
|
default=datetime.fromtimestamp(0))
|
||||||
|
|
||||||
|
def everUsed(self):
|
||||||
|
return self.last_used > datetime.fromtimestamp(0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{}${}".format(self.keyId, self.key)
|
||||||
|
|
||||||
|
def isCorrect(self, timestamp, inpMac):
|
||||||
|
claimedDate = datetime.fromtimestamp(timestamp)
|
||||||
|
if datetime.now() - timedelta(minutes=5) > claimedDate:
|
||||||
|
return False
|
||||||
|
|
||||||
|
mac = hmac.new(self.key,
|
||||||
|
msg=int(claimedDate.timestamp()),
|
||||||
|
digestmod=hashlib.sha256)
|
||||||
|
|
||||||
|
return hmac.compare_digest(mac.hexdigest(), inpMac)
|
||||||
|
|
77
api/views.py
77
api/views.py
|
@ -1,3 +1,76 @@
|
||||||
from django.shortcuts import render
|
from django.http import response
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
# Create your views here.
|
from . import models
|
||||||
|
import mainsite.models as mainModels
|
||||||
|
|
||||||
|
|
||||||
|
def authentify(data):
|
||||||
|
''' returns whether the request's authentification is correct '''
|
||||||
|
required = ['keyId', 'timestamp', 'hmac']
|
||||||
|
for field in required:
|
||||||
|
if field not in data:
|
||||||
|
return response.HttpResponseForbidden(
|
||||||
|
'Missing required field "{}"'.format(field))
|
||||||
|
try:
|
||||||
|
key = models.ApiKey.objects.get(keyId=data['keyId'])
|
||||||
|
except models.ApiKey.DoesNotExist:
|
||||||
|
response.HttpResponseForbidden('Bad authentication')
|
||||||
|
|
||||||
|
if not key.isCorrect(data['timestamp'], data['hmac']):
|
||||||
|
response.HttpResponseForbidden('Bad authentication')
|
||||||
|
|
||||||
|
|
||||||
|
def apiView(fct, required=[]):
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
except json.decoder.JSONDecoreError:
|
||||||
|
return response.HttpResponseBadRequest("Bad json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
authData = data['auth']
|
||||||
|
reqData = data['req']
|
||||||
|
except KeyError:
|
||||||
|
return response.HttpResponseBadRequest("Bad request format")
|
||||||
|
|
||||||
|
for field in required:
|
||||||
|
if field not in reqData:
|
||||||
|
return response.HttpResponseBadRequest(
|
||||||
|
"Missing field {}".format(field))
|
||||||
|
|
||||||
|
authVal = authentify(authData)
|
||||||
|
if authVal is not None:
|
||||||
|
return authVal
|
||||||
|
|
||||||
|
return fct(request, reqData, *args, **kwargs)
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
@apiView(required=["id", "url", "date"])
|
||||||
|
def publishApiView(request, data):
|
||||||
|
''' Publish a BOcal, and create the corresponding year if needed '''
|
||||||
|
if mainModels.Publication.objects.filter(num=data['id']).count() > 0:
|
||||||
|
return response.HttpResponseBadRequest(
|
||||||
|
"Un BOcal du même numéro est déjà présent ! Ajoutez celui-ci à la "
|
||||||
|
"main si vous voulez vraiment faire ça.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
year, month, day = [int(x) for x in data['date'].split('-')]
|
||||||
|
date = datetime.date(year, month, day)
|
||||||
|
except:
|
||||||
|
return response.HttpResponseBadRequest("Bad date")
|
||||||
|
|
||||||
|
pub = mainModels.Publication(num=data['id'],
|
||||||
|
url=data['url'],
|
||||||
|
date=date)
|
||||||
|
try:
|
||||||
|
pub.full_clean()
|
||||||
|
except ValidationError as e:
|
||||||
|
return response.HttpResponseBadRequest(
|
||||||
|
"Invalid data: {}".format(e))
|
||||||
|
pub.save()
|
||||||
|
|
||||||
|
return response.HttpResponse("OK")
|
||||||
|
|
|
@ -30,6 +30,7 @@ INSTALLED_APPS = [
|
||||||
'solo',
|
'solo',
|
||||||
'markdownx',
|
'markdownx',
|
||||||
'mainsite',
|
'mainsite',
|
||||||
|
'api',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
Loading…
Reference in a new issue