Dramatically improved processing of Micropub tokens which supports both the Authorization header and the access_token field approaches

This commit is contained in:
Danielle McLean 2017-12-18 09:51:06 +11:00
parent e5f2e9d537
commit b89405ed88
Signed by untrusted user: 00dani
GPG key ID: 5A5D2D1AFF12EEC5
4 changed files with 71 additions and 27 deletions

View file

@ -1,7 +1,61 @@
from jose import jwt from jose import jwt
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from django.http import HttpResponse
def auth(request):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META.get('HTTP_AUTHORIZATION').split(' ')
if auth[0] != 'Bearer':
return HttpResponse(
'authorisation with {0} not supported'.format(auth[0]),
content_type='text/plain',
status=400
)
if len(auth) != 2:
return HttpResponse(
'invalid Bearer auth format, must be Bearer <token>',
content_type='text/plain',
status=400
)
token = auth[1]
elif 'access_token' in request.POST:
token = request.POST.get('access_token')
elif 'access_token' in request.GET:
token = request.GET.get('access_token')
else:
return HttpResponse(
'authorisation required',
content_type='text/plain',
status=401
)
try:
token = decode(token)
except Exception as e:
return HttpResponse(
'invalid micropub token',
content_type='text/plain',
status=403,
)
return MicropubToken(token)
class MicropubToken:
def __init__(self, tok):
self.user = get_user_model().objects.get(pk=tok['uid'])
self.client = tok['cid']
self.scope = tok['sco']
self.me = self.user.full_url
self.scopes = self.scope.split(' ')
def __contains__(self, scope):
return scope in self.scopes
def encode(payload): def encode(payload):

View file

@ -11,22 +11,13 @@ from lemoncurry import utils
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class TokenView(View): class TokenView(View):
def get(self, req): def get(self, req):
token = req.META.get('HTTP_AUTHORIZATION', '').split(' ') token = tokens.auth(req)
if not token: if hasattr(token, 'content'):
return utils.bad_req('missing Authorization header') return token
if token[0] != 'Bearer':
return utils.bad_req('only Bearer auth is supported')
try:
token = tokens.decode(token[1])
except Exception:
return utils.forbid('invalid token')
user = get_user_model().objects.get(pk=token['uid'])
me = urljoin(utils.origin(req), user.url)
res = { res = {
'me': me, 'me': token.me,
'client_id': token['cid'], 'client_id': token.client,
'scope': token['sco'], 'scope': token.scope,
} }
return utils.choose_type(req, res) return utils.choose_type(req, res)

View file

@ -1,4 +1,3 @@
from django.contrib.auth import get_user_model
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -16,19 +15,14 @@ from lemonauth import tokens
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class MicropubView(View): class MicropubView(View):
def post(self, request): def post(self, request):
auth = request.META.get('HTTP_AUTHORIZATION', '').split(' ') token = tokens.auth(request)
if auth[0] != 'Bearer': if hasattr(token, 'content'):
return utils.bad_req('only Bearer auth supported') return token
try:
token = tokens.decode(auth[1])
except Exception:
return utils.forbid('invalid token')
user = get_user_model().objects.get(pk=token['uid'])
post = request.POST post = request.POST
if post.get('h') != 'entry': if post.get('h') != 'entry':
return utils.bad_req('only h=entry supported') return utils.bad_req('only h=entry supported')
entry = Entry(author=user) entry = Entry(author=token.user)
kind = Note kind = Note
if 'name' in post: if 'name' in post:
entry.name = post['name'] entry.name = post['name']

View file

@ -46,6 +46,11 @@ class User(ModelMeta, AbstractUser):
def get_absolute_url(self): def get_absolute_url(self):
return self.url return self.url
@property
def full_url(self):
base = 'https://' + DjangoSite.objects.get_current().domain
return urljoin(base, self.url)
@property @property
def description(self): def description(self):
return utils.to_plain(self.note) return utils.to_plain(self.note)
@ -74,8 +79,8 @@ class User(ModelMeta, AbstractUser):
return { return {
'@context': 'http://schema.org', '@context': 'http://schema.org',
'@type': 'Person', '@type': 'Person',
'@id': urljoin(base, self.url), '@id': self.full_url,
'url': urljoin(base, self.url), 'url': self.full_url,
'name': self.name, 'name': self.name,
'email': self.email, 'email': self.email,
'image': urljoin(base, self.avatar.url), 'image': urljoin(base, self.avatar.url),