Browse Source

Refactor the Micropub error responses into a non-view module, have them produce an immediately raise-able exception

tags/v1.10.0
Danielle McLean 1 year ago
parent
commit
d68dda85ad
Signed by: Danielle McLean <dani@00dani.me> GPG Key ID: 8EB789DDF3ABD240

+ 6
- 7
entries/from_url.py View File

@@ -3,8 +3,7 @@ from urllib.parse import urlparse
3 3
 from django.contrib.sites.models import Site
4 4
 from django.http import HttpResponse
5 5
 from django.urls import resolve, Resolver404
6
-from micropub.views import error
7
-from lemoncurry.middleware import ResponseException
6
+from micropub import error
8 7
 
9 8
 from .models import Entry
10 9
 
@@ -12,24 +11,24 @@ from .models import Entry
12 11
 def from_url(url: str) -> Entry:
13 12
     domain = Site.objects.get_current().domain
14 13
     if not url:
15
-        raise ResponseException(error.bad_req('url parameter required'))
14
+        raise error.bad_req('url parameter required')
16 15
     if '//' not in url:
17 16
         url = '//' + url
18 17
     parts = urlparse(url, scheme='https')
19 18
     if parts.scheme not in ('http', 'https') or parts.netloc != domain:
20
-        raise ResponseException(error.bad_req('url does not point to this site'))
19
+        raise error.bad_req('url does not point to this site')
21 20
 
22 21
     try:
23 22
         match = resolve(parts.path)
24 23
     except Resolver404:
25
-        raise ResponseException(error.bad_req('url does not point to a valid page on this site'))
24
+        raise error.bad_req('url does not point to a valid page on this site')
26 25
 
27 26
     if match.view_name != 'entries:entry':
28
-        raise ResponseException(error.bad_req('url does not point to an entry on this site'))
27
+        raise error.bad_req('url does not point to an entry on this site')
29 28
 
30 29
     try:
31 30
         entry = Entry.objects.get(pk=match.kwargs['id'])
32 31
     except Entry.DoesNotExist:
33
-        raise ResponseException(error.bad_req('url does not point to an existing entry'))
32
+        raise error.bad_req('url does not point to an existing entry')
34 33
 
35 34
     return entry

+ 6
- 11
lemonauth/tokens.py View File

@@ -1,31 +1,26 @@
1
-from lemoncurry.middleware import ResponseException
2
-from micropub.views import error
1
+from micropub import error
3 2
 from .models import IndieAuthCode, Token
4 3
 
5 4
 
6
-def auth(request):
5
+def auth(request) -> Token:
7 6
     if 'HTTP_AUTHORIZATION' in request.META:
8 7
         auth = request.META.get('HTTP_AUTHORIZATION').split(' ')
9 8
         if auth[0] != 'Bearer':
10
-            raise ResponseException(error.bad_req(
11
-                'auth type {0} not supported'.format(auth[0])
12
-            ))
9
+            raise error.bad_req('auth type {0} not supported'.format(auth[0]))
13 10
         if len(auth) != 2:
14
-            raise ResponseException(error.bad_req(
15
-                'invalid Bearer auth format, must be Bearer <token>'
16
-            ))
11
+            raise error.bad_req('invalid Bearer auth format, must be Bearer <token>')
17 12
         token = auth[1]
18 13
     elif 'access_token' in request.POST:
19 14
         token = request.POST.get('access_token')
20 15
     elif 'access_token' in request.GET:
21 16
         token = request.GET.get('access_token')
22 17
     else:
23
-        raise ResponseException(error.unauthorized())
18
+        raise error.unauthorized()
24 19
 
25 20
     try:
26 21
         token = Token.objects.get(pk=token)
27 22
     except Token.DoesNotExist:
28
-        raise ResponseException(error.forbidden())
23
+        raise error.forbidden()
29 24
 
30 25
     return token
31 26
 

+ 36
- 0
micropub/error.py View File

@@ -0,0 +1,36 @@
1
+from django.http import JsonResponse
2
+from lemoncurry.middleware import ResponseException
3
+from typing import Optional
4
+
5
+
6
+def forbidden() -> ResponseException:
7
+    return res('forbidden', 403)
8
+
9
+
10
+def unauthorized() -> ResponseException:
11
+    return res('unauthorized', 401)
12
+
13
+
14
+def bad_req(msg: str) -> ResponseException:
15
+    return res('invalid_request', msg=msg)
16
+
17
+
18
+def bad_type(type: str) -> ResponseException:
19
+    msg = 'unsupported request type {0}'.format(type)
20
+    return res('invalid_request', 415, msg)
21
+
22
+
23
+def bad_scope(scope: str) -> ResponseException:
24
+    return res('insufficient_scope', 401, scope=scope)
25
+
26
+
27
+def res(error: str,
28
+        status: Optional[int]=400,
29
+        msg: Optional[str]=None,
30
+        scope: Optional[str]=None):
31
+    content = {'error': error}
32
+    if msg is not None:
33
+        content['error_description'] = msg
34
+    if scope:
35
+        content['scope'] = scope
36
+    return ResponseException(JsonResponse(content, status=status))

+ 3
- 3
micropub/views/__init__.py View File

@@ -26,6 +26,6 @@ def micropub(request):
26 26
     if request.content_type == 'application/json':
27 27
         request.json = json.load(request)
28 28
         action = request.json.get('action', 'create')
29
-    if action in actions:
30
-        return actions[action](request)
31
-    return error.bad_req('unknown action: {}'.format(action))
29
+    if action not in actions:
30
+        raise error.bad_req('unknown action: {}'.format(action))
31
+    return actions[action](request)

+ 5
- 5
micropub/views/create.py View File

@@ -7,7 +7,7 @@ from entries.models import Cat, Entry
7 7
 from entries.kinds import Article, Note, Reply, Like, Repost
8 8
 from lemoncurry import utils
9 9
 
10
-from . import error
10
+from .. import error
11 11
 
12 12
 
13 13
 def form_to_mf2(request):
@@ -33,14 +33,14 @@ def create(request):
33 33
         'application/x-www-form-urlencoded': form_to_mf2,
34 34
     }
35 35
     if 'create' not in request.token:
36
-        return error.bad_scope('create')
36
+        raise error.bad_scope('create')
37 37
     if request.content_type not in normalise:
38
-        return error.unsupported_type(request.content_type)
38
+        raise error.unsupported_type(request.content_type)
39 39
     body = normalise[request.content_type](request)
40 40
     if 'type' not in body:
41
-        return error.bad_req('mf2 object type required')
41
+        raise error.bad_req('mf2 object type required')
42 42
     if body['type'] != ['h-entry']:
43
-        return error.bad_req('only h-entry supported')
43
+        raise error.bad_req('only h-entry supported')
44 44
 
45 45
     entry = Entry(author=request.token.user)
46 46
     props = body.get('properties', {})

+ 4
- 4
micropub/views/delete.py View File

@@ -4,7 +4,7 @@ from ronkyuu import webmention
4 4
 from entries.from_url import from_url
5 5
 from entries.jobs import ping_hub, send_mentions
6 6
 
7
-from . import error
7
+from .. import error
8 8
 
9 9
 def delete(request):
10 10
     normalise = {
@@ -12,14 +12,14 @@ def delete(request):
12 12
         'application/x-www-form-urlencoded': lambda r: r.POST.get('url'),
13 13
     }
14 14
     if 'delete' not in request.token:
15
-        return error.bad_scope('delete')
15
+        raise error.bad_scope('delete')
16 16
     if request.content_type not in normalise:
17
-        return error.unsupported_type(request.content_type)
17
+        raise error.unsupported_type(request.content_type)
18 18
     url = normalise[request.content_type](request)
19 19
     entry = from_url(url)
20 20
 
21 21
     if entry.author != request.token.user:
22
-        return error.forbid('entry belongs to another user')
22
+        raise error.forbid('entry belongs to another user')
23 23
 
24 24
     perma = entry.absolute_url
25 25
     pings = entry.affected_urls

+ 0
- 31
micropub/views/error.py View File

@@ -1,31 +0,0 @@
1
-from django.http import JsonResponse
2
-
3
-
4
-def forbidden():
5
-    return res('forbidden', 403)
6
-
7
-
8
-def unauthorized():
9
-    return res('unauthorized', 401)
10
-
11
-
12
-def bad_req(msg):
13
-    return res('invalid_request', msg=msg)
14
-
15
-
16
-def bad_type(type):
17
-    msg = 'unsupported request type {0}'.format(type)
18
-    return res('invalid_request', 415, msg)
19
-
20
-
21
-def bad_scope(scope):
22
-    return res('insufficient_scope', 401, scope=scope)
23
-
24
-
25
-def res(error, status=400, msg=None, scope=None):
26
-    content = {'error': error}
27
-    if msg:
28
-        content['error_description'] = msg
29
-    if scope:
30
-        content['scope'] = scope
31
-    return JsonResponse(content, status=status)

+ 4
- 4
micropub/views/media.py View File

@@ -8,7 +8,7 @@ import magic
8 8
 
9 9
 from lemonauth import tokens
10 10
 from lemoncurry.utils import absolute_url
11
-from . import error
11
+from .. import error
12 12
 
13 13
 ACCEPTED_MEDIA_TYPES = (
14 14
     'image/gif',
@@ -22,12 +22,12 @@ ACCEPTED_MEDIA_TYPES = (
22 22
 def media(request):
23 23
     token = tokens.auth(request)
24 24
     if 'file' not in request.FILES:
25
-        return error.bad_req(
25
+        raise error.bad_req(
26 26
             "a file named 'file' must be provided to the media endpoint"
27 27
         )
28 28
     file = request.FILES['file']
29 29
     if file.content_type not in ACCEPTED_MEDIA_TYPES:
30
-        return error.bad_req(
30
+        raise error.bad_req(
31 31
             'unacceptable file type {0}'.format(file.content_type)
32 32
         )
33 33
 
@@ -39,7 +39,7 @@ def media(request):
39 39
         sha.update(chunk)
40 40
 
41 41
     if mime != file.content_type:
42
-        return error.bad_req(
42
+        raise error.bad_req(
43 43
             'detected file type {0} did not match specified file type {1}'
44 44
             .format(mime, file.content_type)
45 45
         )

+ 5
- 8
micropub/views/query.py View File

@@ -1,9 +1,8 @@
1 1
 from django.http import JsonResponse
2 2
 from django.urls import reverse
3 3
 from lemoncurry import requests
4
-from lemoncurry.middleware import ResponseException
5 4
 from lemoncurry.utils import absolute_url
6
-from . import error
5
+from .. import error
7 6
 
8 7
 
9 8
 def config(request):
@@ -15,12 +14,10 @@ def config(request):
15 14
 def source(request):
16 15
     get = request.GET
17 16
     if 'url' not in get:
18
-        raise ResponseException(error.bad_req(
19
-            'must specify url parameter for source query'
20
-        ))
17
+        raise error.bad_req('must specify url parameter for source query')
21 18
     mf2 = requests.mf2(get['url']).to_dict(filter_by_type='h-entry')
22 19
     if not mf2:
23
-        raise ResponseException(error.bad_req('no h-entry at the requested url'))
20
+        raise error.bad_req('no h-entry at the requested url')
24 21
     entry = mf2[0]
25 22
     keys = get.getlist('properties', []) + get.getlist('properties[]', [])
26 23
     if not keys:
@@ -43,9 +40,9 @@ queries = {
43 40
 
44 41
 def query(request):
45 42
     if 'q' not in request.GET:
46
-        return error.bad_req('must specify q parameter')
43
+        raise error.bad_req('must specify q parameter')
47 44
     q = request.GET['q']
48 45
     if q not in queries:
49
-        return error.bad_req('unsupported query {0}'.format(q))
46
+        raise error.bad_req('unsupported query {0}'.format(q))
50 47
     res = queries[q](request)
51 48
     return JsonResponse(res)

Loading…
Cancel
Save