Compare commits

..

No commits in common. "d68dda85ad855e9aba6fff7dc7c0b750a56def6e" and "7d17a92793c0c9a191f785bafce6986232957f49" have entirely different histories.

13 changed files with 86 additions and 73 deletions

View file

@ -3,7 +3,8 @@ 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 import error
from micropub.views import error
from lemoncurry.middleware import ResponseException
from .models import Entry
@ -11,24 +12,24 @@ from .models import Entry
def from_url(url: str) -> Entry:
domain = Site.objects.get_current().domain
if not url:
raise error.bad_req('url parameter required')
raise ResponseException(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 error.bad_req('url does not point to this site')
raise ResponseException(error.bad_req('url does not point to this site'))
try:
match = resolve(parts.path)
except Resolver404:
raise error.bad_req('url does not point to a valid page on this site')
raise ResponseException(error.bad_req('url does not point to a valid page on this site'))
if match.view_name != 'entries:entry':
raise error.bad_req('url does not point to an entry on this site')
raise ResponseException(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 error.bad_req('url does not point to an existing entry')
raise ResponseException(error.bad_req('url does not point to an existing entry'))
return entry

View file

@ -1,8 +1,6 @@
from django.core.paginator import Paginator
from django.shortcuts import redirect
from lemoncurry.middleware import ResponseException
def paginate(queryset, reverse, page):
class Page:
@ -20,7 +18,7 @@ def paginate(queryset, reverse, page):
# If the first page was requested, redirect to the clean version of the URL
# with no page suffix.
if page == 1:
raise ResponseException(redirect(Page(1).url))
return redirect(Page(1).url)
paginator = Paginator(queryset, 10)
entries = paginator.page(page or 1)

View file

@ -9,6 +9,8 @@ from ..pagination import paginate
def by_kind(request, kind, page=None):
entries = Entry.objects.filter(kind=kind.id)
entries = paginate(queryset=entries, reverse=kind.index_page, page=page)
if hasattr(entries, 'content'):
return entries
return {
'entries': entries,
@ -29,6 +31,8 @@ def by_cat(request, slug, page=None):
cat = get_object_or_404(Cat, slug=slug)
entries = cat.entries.all()
entries = paginate(queryset=entries, reverse=url, page=page)
if hasattr(entries, 'content'):
return entries
return {
'entries': entries,

View file

@ -24,6 +24,10 @@ def index(request, page=None):
entries = user.entries.filter(kind__in=kinds.on_home)
entries = pagination.paginate(queryset=entries, reverse=url, page=page)
# If we got a valid HTTP response, just return it without rendering.
if hasattr(entries, 'content'):
return entries
return {
'user': user,
'entries': entries,

View file

@ -1,26 +1,28 @@
from micropub import error
from micropub.views import error
from .models import IndieAuthCode, Token
def auth(request) -> Token:
def auth(request):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META.get('HTTP_AUTHORIZATION').split(' ')
if auth[0] != 'Bearer':
raise error.bad_req('auth type {0} not supported'.format(auth[0]))
return error.bad_req('auth type {0} not supported'.format(auth[0]))
if len(auth) != 2:
raise error.bad_req('invalid Bearer auth format, must be Bearer <token>')
return 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 error.unauthorized()
return error.unauthorized()
try:
token = Token.objects.get(pk=token)
except Token.DoesNotExist:
raise error.forbidden()
return error.forbidden()
return token

View file

@ -11,6 +11,8 @@ from lemoncurry import utils
class TokenView(View):
def get(self, req):
token = tokens.auth(req)
if hasattr(token, 'content'):
return token
res = {
'me': token.me,
'client_id': token.client_id,

View file

@ -1,36 +0,0 @@
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))

View file

@ -18,7 +18,10 @@ actions = {
@csrf_exempt
@require_http_methods(['GET', 'HEAD', 'POST'])
def micropub(request):
request.token = tokens.auth(request)
token = tokens.auth(request)
if hasattr(token, 'content'):
return token
request.token = token
if request.method in ('GET', 'HEAD'):
return query(request)
@ -26,6 +29,6 @@ def micropub(request):
if request.content_type == 'application/json':
request.json = json.load(request)
action = request.json.get('action', 'create')
if action not in actions:
raise error.bad_req('unknown action: {}'.format(action))
return actions[action](request)
if action in actions:
return actions[action](request)
return error.bad_req('unknown action: {}'.format(action))

View file

@ -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:
raise error.bad_scope('create')
return error.bad_scope('create')
if request.content_type not in normalise:
raise error.unsupported_type(request.content_type)
return error.unsupported_type(request.content_type)
body = normalise[request.content_type](request)
if 'type' not in body:
raise error.bad_req('mf2 object type required')
return error.bad_req('mf2 object type required')
if body['type'] != ['h-entry']:
raise error.bad_req('only h-entry supported')
return error.bad_req('only h-entry supported')
entry = Entry(author=request.token.user)
props = body.get('properties', {})

View file

@ -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:
raise error.bad_scope('delete')
return error.bad_scope('delete')
if request.content_type not in normalise:
raise error.unsupported_type(request.content_type)
return error.unsupported_type(request.content_type)
url = normalise[request.content_type](request)
entry = from_url(url)
if entry.author != request.token.user:
raise error.forbid('entry belongs to another user')
return error.forbid('entry belongs to another user')
perma = entry.absolute_url
pings = entry.affected_urls

31
micropub/views/error.py Normal file
View file

@ -0,0 +1,31 @@
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)

View file

@ -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',
@ -21,13 +21,15 @@ ACCEPTED_MEDIA_TYPES = (
@require_POST
def media(request):
token = tokens.auth(request)
if hasattr(token, 'content'):
return token
if 'file' not in request.FILES:
raise error.bad_req(
return 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:
raise error.bad_req(
return error.bad_req(
'unacceptable file type {0}'.format(file.content_type)
)
@ -39,7 +41,7 @@ def media(request):
sha.update(chunk)
if mime != file.content_type:
raise error.bad_req(
return error.bad_req(
'detected file type {0} did not match specified file type {1}'
.format(mime, file.content_type)
)

View file

@ -2,7 +2,7 @@ from django.http import JsonResponse
from django.urls import reverse
from lemoncurry import requests
from lemoncurry.utils import absolute_url
from .. import error
from . import error
def config(request):
@ -14,10 +14,10 @@ def config(request):
def source(request):
get = request.GET
if 'url' not in get:
raise error.bad_req('must specify url parameter for source query')
return 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 error.bad_req('no h-entry at the requested url')
return error.bad_req('no h-entry at the requested url')
entry = mf2[0]
keys = get.getlist('properties', []) + get.getlist('properties[]', [])
if not keys:
@ -40,9 +40,11 @@ queries = {
def query(request):
if 'q' not in request.GET:
raise error.bad_req('must specify q parameter')
return error.bad_req('must specify q parameter')
q = request.GET['q']
if q not in queries:
raise error.bad_req('unsupported query {0}'.format(q))
return error.bad_req('unsupported query {0}'.format(q))
res = queries[q](request)
if hasattr(res, 'content'):
return res
return JsonResponse(res)