diff --git a/entries/from_url.py b/entries/from_url.py index b19475f..d842b83 100644 --- a/entries/from_url.py +++ b/entries/from_url.py @@ -3,8 +3,7 @@ from urllib.parse import urlparse from django.contrib.sites.models import Site from django.http import HttpResponse from django.urls import resolve, Resolver404 -from micropub.views import error -from lemoncurry.middleware import ResponseException +from micropub import error from .models import Entry @@ -12,24 +11,24 @@ from .models import Entry def from_url(url: str) -> Entry: domain = Site.objects.get_current().domain if not url: - raise ResponseException(error.bad_req('url parameter required')) + raise error.bad_req('url parameter required') if '//' not in url: url = '//' + url parts = urlparse(url, scheme='https') if parts.scheme not in ('http', 'https') or parts.netloc != domain: - raise ResponseException(error.bad_req('url does not point to this site')) + raise error.bad_req('url does not point to this site') try: match = resolve(parts.path) except Resolver404: - raise ResponseException(error.bad_req('url does not point to a valid page on this site')) + raise error.bad_req('url does not point to a valid page on this site') if match.view_name != 'entries:entry': - raise ResponseException(error.bad_req('url does not point to an entry on this site')) + raise error.bad_req('url does not point to an entry on this site') try: entry = Entry.objects.get(pk=match.kwargs['id']) except Entry.DoesNotExist: - raise ResponseException(error.bad_req('url does not point to an existing entry')) + raise error.bad_req('url does not point to an existing entry') return entry diff --git a/lemonauth/tokens.py b/lemonauth/tokens.py index 2ea16ff..5e6640e 100644 --- a/lemonauth/tokens.py +++ b/lemonauth/tokens.py @@ -1,31 +1,26 @@ -from lemoncurry.middleware import ResponseException -from micropub.views import error +from micropub import error from .models import IndieAuthCode, Token -def auth(request): +def auth(request) -> Token: if 'HTTP_AUTHORIZATION' in request.META: auth = request.META.get('HTTP_AUTHORIZATION').split(' ') if auth[0] != 'Bearer': - raise ResponseException(error.bad_req( - 'auth type {0} not supported'.format(auth[0]) - )) + raise error.bad_req('auth type {0} not supported'.format(auth[0])) if len(auth) != 2: - raise ResponseException(error.bad_req( - 'invalid Bearer auth format, must be Bearer ' - )) + raise error.bad_req('invalid Bearer auth format, must be Bearer ') 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: - raise ResponseException(error.unauthorized()) + raise error.unauthorized() try: token = Token.objects.get(pk=token) except Token.DoesNotExist: - raise ResponseException(error.forbidden()) + raise error.forbidden() return token diff --git a/micropub/error.py b/micropub/error.py new file mode 100644 index 0000000..a5e9373 --- /dev/null +++ b/micropub/error.py @@ -0,0 +1,36 @@ +from django.http import JsonResponse +from lemoncurry.middleware import ResponseException +from typing import Optional + + +def forbidden() -> ResponseException: + return res('forbidden', 403) + + +def unauthorized() -> ResponseException: + return res('unauthorized', 401) + + +def bad_req(msg: str) -> ResponseException: + return res('invalid_request', msg=msg) + + +def bad_type(type: str) -> ResponseException: + msg = 'unsupported request type {0}'.format(type) + return res('invalid_request', 415, msg) + + +def bad_scope(scope: str) -> ResponseException: + return res('insufficient_scope', 401, scope=scope) + + +def res(error: str, + status: Optional[int]=400, + msg: Optional[str]=None, + scope: Optional[str]=None): + content = {'error': error} + if msg is not None: + content['error_description'] = msg + if scope: + content['scope'] = scope + return ResponseException(JsonResponse(content, status=status)) diff --git a/micropub/views/__init__.py b/micropub/views/__init__.py index 7998379..7ff7878 100644 --- a/micropub/views/__init__.py +++ b/micropub/views/__init__.py @@ -26,6 +26,6 @@ def micropub(request): if request.content_type == 'application/json': request.json = json.load(request) action = request.json.get('action', 'create') - if action in actions: - return actions[action](request) - return error.bad_req('unknown action: {}'.format(action)) + if action not in actions: + raise error.bad_req('unknown action: {}'.format(action)) + return actions[action](request) diff --git a/micropub/views/create.py b/micropub/views/create.py index 6c4cf21..23bd21e 100644 --- a/micropub/views/create.py +++ b/micropub/views/create.py @@ -7,7 +7,7 @@ from entries.models import Cat, Entry from entries.kinds import Article, Note, Reply, Like, Repost from lemoncurry import utils -from . import error +from .. import error def form_to_mf2(request): @@ -33,14 +33,14 @@ def create(request): 'application/x-www-form-urlencoded': form_to_mf2, } if 'create' not in request.token: - return error.bad_scope('create') + raise error.bad_scope('create') if request.content_type not in normalise: - return error.unsupported_type(request.content_type) + raise error.unsupported_type(request.content_type) body = normalise[request.content_type](request) if 'type' not in body: - return error.bad_req('mf2 object type required') + raise error.bad_req('mf2 object type required') if body['type'] != ['h-entry']: - return error.bad_req('only h-entry supported') + raise error.bad_req('only h-entry supported') entry = Entry(author=request.token.user) props = body.get('properties', {}) diff --git a/micropub/views/delete.py b/micropub/views/delete.py index 4fc1f3e..035da7e 100644 --- a/micropub/views/delete.py +++ b/micropub/views/delete.py @@ -4,7 +4,7 @@ from ronkyuu import webmention from entries.from_url import from_url from entries.jobs import ping_hub, send_mentions -from . import error +from .. import error def delete(request): normalise = { @@ -12,14 +12,14 @@ def delete(request): 'application/x-www-form-urlencoded': lambda r: r.POST.get('url'), } if 'delete' not in request.token: - return error.bad_scope('delete') + raise error.bad_scope('delete') if request.content_type not in normalise: - return error.unsupported_type(request.content_type) + raise error.unsupported_type(request.content_type) url = normalise[request.content_type](request) entry = from_url(url) if entry.author != request.token.user: - return error.forbid('entry belongs to another user') + raise error.forbid('entry belongs to another user') perma = entry.absolute_url pings = entry.affected_urls diff --git a/micropub/views/error.py b/micropub/views/error.py deleted file mode 100644 index 1c7b1a8..0000000 --- a/micropub/views/error.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.http import JsonResponse - - -def forbidden(): - return res('forbidden', 403) - - -def unauthorized(): - return res('unauthorized', 401) - - -def bad_req(msg): - return res('invalid_request', msg=msg) - - -def bad_type(type): - msg = 'unsupported request type {0}'.format(type) - return res('invalid_request', 415, msg) - - -def bad_scope(scope): - return res('insufficient_scope', 401, scope=scope) - - -def res(error, status=400, msg=None, scope=None): - content = {'error': error} - if msg: - content['error_description'] = msg - if scope: - content['scope'] = scope - return JsonResponse(content, status=status) diff --git a/micropub/views/media.py b/micropub/views/media.py index 85a22a0..db59972 100644 --- a/micropub/views/media.py +++ b/micropub/views/media.py @@ -8,7 +8,7 @@ import magic from lemonauth import tokens from lemoncurry.utils import absolute_url -from . import error +from .. import error ACCEPTED_MEDIA_TYPES = ( 'image/gif', @@ -22,12 +22,12 @@ ACCEPTED_MEDIA_TYPES = ( def media(request): token = tokens.auth(request) if 'file' not in request.FILES: - return error.bad_req( + raise error.bad_req( "a file named 'file' must be provided to the media endpoint" ) file = request.FILES['file'] if file.content_type not in ACCEPTED_MEDIA_TYPES: - return error.bad_req( + raise error.bad_req( 'unacceptable file type {0}'.format(file.content_type) ) @@ -39,7 +39,7 @@ def media(request): sha.update(chunk) if mime != file.content_type: - return error.bad_req( + raise error.bad_req( 'detected file type {0} did not match specified file type {1}' .format(mime, file.content_type) ) diff --git a/micropub/views/query.py b/micropub/views/query.py index 2dcd9ac..ded7775 100644 --- a/micropub/views/query.py +++ b/micropub/views/query.py @@ -1,9 +1,8 @@ from django.http import JsonResponse from django.urls import reverse from lemoncurry import requests -from lemoncurry.middleware import ResponseException from lemoncurry.utils import absolute_url -from . import error +from .. import error def config(request): @@ -15,12 +14,10 @@ def config(request): def source(request): get = request.GET if 'url' not in get: - raise ResponseException(error.bad_req( - 'must specify url parameter for source query' - )) + raise error.bad_req('must specify url parameter for source query') mf2 = requests.mf2(get['url']).to_dict(filter_by_type='h-entry') if not mf2: - raise ResponseException(error.bad_req('no h-entry at the requested url')) + raise error.bad_req('no h-entry at the requested url') entry = mf2[0] keys = get.getlist('properties', []) + get.getlist('properties[]', []) if not keys: @@ -43,9 +40,9 @@ queries = { def query(request): if 'q' not in request.GET: - return error.bad_req('must specify q parameter') + raise error.bad_req('must specify q parameter') q = request.GET['q'] if q not in queries: - return error.bad_req('unsupported query {0}'.format(q)) + raise error.bad_req('unsupported query {0}'.format(q)) res = queries[q](request) return JsonResponse(res)