Compare commits
No commits in common. "d68dda85ad855e9aba6fff7dc7c0b750a56def6e" and "7d17a92793c0c9a191f785bafce6986232957f49" have entirely different histories.
d68dda85ad
...
7d17a92793
13 changed files with 86 additions and 73 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
|
@ -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))
|
||||
if action in actions:
|
||||
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:
|
||||
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', {})
|
||||
|
|
|
@ -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
31
micropub/views/error.py
Normal 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)
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue