From b89405ed88b19c881e1947c5a4e3b2a9ae522314 Mon Sep 17 00:00:00 2001 From: Danielle McLean Date: Mon, 18 Dec 2017 09:51:06 +1100 Subject: [PATCH] Dramatically improved processing of Micropub tokens which supports both the Authorization header and the access_token field approaches --- lemonauth/tokens.py | 54 ++++++++++++++++++++++++++++++++++++++++ lemonauth/views/token.py | 21 +++++----------- micropub/views.py | 14 +++-------- users/models.py | 9 +++++-- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/lemonauth/tokens.py b/lemonauth/tokens.py index a6ecc53..949acd5 100644 --- a/lemonauth/tokens.py +++ b/lemonauth/tokens.py @@ -1,7 +1,61 @@ from jose import jwt from datetime import datetime, timedelta +from django.contrib.auth import get_user_model 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 ', + 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): diff --git a/lemonauth/views/token.py b/lemonauth/views/token.py index 83dc5ef..cf00966 100644 --- a/lemonauth/views/token.py +++ b/lemonauth/views/token.py @@ -11,22 +11,13 @@ from lemoncurry import utils @method_decorator(csrf_exempt, name='dispatch') class TokenView(View): def get(self, req): - token = req.META.get('HTTP_AUTHORIZATION', '').split(' ') - if not token: - return utils.bad_req('missing Authorization header') - 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) + token = tokens.auth(req) + if hasattr(token, 'content'): + return token res = { - 'me': me, - 'client_id': token['cid'], - 'scope': token['sco'], + 'me': token.me, + 'client_id': token.client, + 'scope': token.scope, } return utils.choose_type(req, res) diff --git a/micropub/views.py b/micropub/views.py index 981b5fd..e20a480 100644 --- a/micropub/views.py +++ b/micropub/views.py @@ -1,4 +1,3 @@ -from django.contrib.auth import get_user_model from django.http import HttpResponse from django.urls import reverse from django.utils.decorators import method_decorator @@ -16,19 +15,14 @@ from lemonauth import tokens @method_decorator(csrf_exempt, name='dispatch') class MicropubView(View): def post(self, request): - auth = request.META.get('HTTP_AUTHORIZATION', '').split(' ') - if auth[0] != 'Bearer': - return utils.bad_req('only Bearer auth supported') - try: - token = tokens.decode(auth[1]) - except Exception: - return utils.forbid('invalid token') - user = get_user_model().objects.get(pk=token['uid']) + token = tokens.auth(request) + if hasattr(token, 'content'): + return token post = request.POST if post.get('h') != 'entry': return utils.bad_req('only h=entry supported') - entry = Entry(author=user) + entry = Entry(author=token.user) kind = Note if 'name' in post: entry.name = post['name'] diff --git a/users/models.py b/users/models.py index 49caeff..97cdf87 100644 --- a/users/models.py +++ b/users/models.py @@ -46,6 +46,11 @@ class User(ModelMeta, AbstractUser): def get_absolute_url(self): return self.url + @property + def full_url(self): + base = 'https://' + DjangoSite.objects.get_current().domain + return urljoin(base, self.url) + @property def description(self): return utils.to_plain(self.note) @@ -74,8 +79,8 @@ class User(ModelMeta, AbstractUser): return { '@context': 'http://schema.org', '@type': 'Person', - '@id': urljoin(base, self.url), - 'url': urljoin(base, self.url), + '@id': self.full_url, + 'url': self.full_url, 'name': self.name, 'email': self.email, 'image': urljoin(base, self.avatar.url),