forked from 00dani/lemoncurry
Refactor the Micropub error responses into a non-view module, have them produce an immediately raise-able exception
This commit is contained in:
parent
065619772e
commit
d68dda85ad
9 changed files with 69 additions and 73 deletions
|
@ -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
|
||||
|
|
|
@ -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 <token>'
|
||||
))
|
||||
raise error.bad_req('invalid Bearer auth format, must be Bearer <token>')
|
||||
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
|
||||
|
||||
|
|
36
micropub/error.py
Normal file
36
micropub/error.py
Normal file
|
@ -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))
|
|
@ -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:
|
||||
if action not in actions:
|
||||
raise error.bad_req('unknown action: {}'.format(action))
|
||||
return actions[action](request)
|
||||
return error.bad_req('unknown action: {}'.format(action))
|
||||
|
|
|
@ -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', {})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue