forked from mirrors/bookwyrm
Recursively parse activities
This commit is contained in:
parent
fd19b55961
commit
e810c2bee0
4 changed files with 24 additions and 63 deletions
|
@ -2,7 +2,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .base_activity import ActivityEncoder, Signature
|
from .base_activity import ActivityEncoder, Signature, naive_parse
|
||||||
from .base_activity import Link, Mention
|
from .base_activity import Link, Mention
|
||||||
from .base_activity import ActivitySerializerError, resolve_remote_id
|
from .base_activity import ActivitySerializerError, resolve_remote_id
|
||||||
from .image import Image
|
from .image import Image
|
||||||
|
@ -23,3 +23,7 @@ from .verbs import Add, AddBook, AddListItem, Remove
|
||||||
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
||||||
activity_objects = {c[0]: c[1] for c in cls_members \
|
activity_objects = {c[0]: c[1] for c in cls_members \
|
||||||
if hasattr(c[1], 'to_model')}
|
if hasattr(c[1], 'to_model')}
|
||||||
|
|
||||||
|
def parse(activity_json):
|
||||||
|
''' figure out what activity this is and parse it '''
|
||||||
|
return naive_parse(activity_objects, activity_json)
|
||||||
|
|
|
@ -40,6 +40,17 @@ class Signature:
|
||||||
signatureValue: str
|
signatureValue: str
|
||||||
type: str = 'RsaSignature2017'
|
type: str = 'RsaSignature2017'
|
||||||
|
|
||||||
|
def naive_parse(activity_objects, activity_json):
|
||||||
|
''' this navigates circular import issues '''
|
||||||
|
try:
|
||||||
|
activity_type = activity_json['type']
|
||||||
|
print(activity_type)
|
||||||
|
serializer = activity_objects[activity_type]
|
||||||
|
print(serializer)
|
||||||
|
except KeyError:
|
||||||
|
raise ActivitySerializerError('Invalid type "%s"' % activity_type)
|
||||||
|
|
||||||
|
return serializer(activity_objects=activity_objects, **activity_json)
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class ActivityObject:
|
class ActivityObject:
|
||||||
|
@ -47,13 +58,17 @@ class ActivityObject:
|
||||||
id: str
|
id: str
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, activity_objects=None, **kwargs):
|
||||||
''' this lets you pass in an object with fields that aren't in the
|
''' this lets you pass in an object with fields that aren't in the
|
||||||
dataclass, which it ignores. Any field in the dataclass is required or
|
dataclass, which it ignores. Any field in the dataclass is required or
|
||||||
has a default value '''
|
has a default value '''
|
||||||
for field in fields(self):
|
for field in fields(self):
|
||||||
try:
|
try:
|
||||||
|
print(field.name, field.type)
|
||||||
value = kwargs[field.name]
|
value = kwargs[field.name]
|
||||||
|
if field.type == 'ActivityObject' and activity_objects:
|
||||||
|
value = naive_parse(activity_objects, value)
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if field.default == MISSING and \
|
if field.default == MISSING and \
|
||||||
field.default_factory == MISSING:
|
field.default_factory == MISSING:
|
||||||
|
|
|
@ -40,60 +40,6 @@ class Incoming(TestCase):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
|
||||||
def test_inbox_invalid_get(self):
|
|
||||||
''' shouldn't try to handle if the user is not found '''
|
|
||||||
request = self.factory.get('https://www.example.com/')
|
|
||||||
self.assertIsInstance(
|
|
||||||
incoming.inbox(request, 'anything'), HttpResponseNotAllowed)
|
|
||||||
self.assertIsInstance(
|
|
||||||
incoming.shared_inbox(request), HttpResponseNotAllowed)
|
|
||||||
|
|
||||||
def test_inbox_invalid_user(self):
|
|
||||||
''' shouldn't try to handle if the user is not found '''
|
|
||||||
request = self.factory.post('https://www.example.com/')
|
|
||||||
self.assertIsInstance(
|
|
||||||
incoming.inbox(request, 'fish@tomato.com'), HttpResponseNotFound)
|
|
||||||
|
|
||||||
def test_inbox_invalid_no_object(self):
|
|
||||||
''' json is missing "object" field '''
|
|
||||||
request = self.factory.post(
|
|
||||||
self.local_user.shared_inbox, data={})
|
|
||||||
self.assertIsInstance(
|
|
||||||
incoming.shared_inbox(request), HttpResponseBadRequest)
|
|
||||||
|
|
||||||
def test_inbox_invalid_bad_signature(self):
|
|
||||||
''' bad request for invalid signature '''
|
|
||||||
request = self.factory.post(
|
|
||||||
self.local_user.shared_inbox,
|
|
||||||
'{"type": "Test", "object": "exists"}',
|
|
||||||
content_type='application/json')
|
|
||||||
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
|
|
||||||
mock_has_valid.return_value = False
|
|
||||||
self.assertEqual(
|
|
||||||
incoming.shared_inbox(request).status_code, 401)
|
|
||||||
|
|
||||||
def test_inbox_invalid_bad_signature_delete(self):
|
|
||||||
''' invalid signature for Delete is okay though '''
|
|
||||||
request = self.factory.post(
|
|
||||||
self.local_user.shared_inbox,
|
|
||||||
'{"type": "Delete", "object": "exists"}',
|
|
||||||
content_type='application/json')
|
|
||||||
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
|
|
||||||
mock_has_valid.return_value = False
|
|
||||||
self.assertEqual(
|
|
||||||
incoming.shared_inbox(request).status_code, 200)
|
|
||||||
|
|
||||||
def test_inbox_unknown_type(self):
|
|
||||||
''' never heard of that activity type, don't have a handler for it '''
|
|
||||||
request = self.factory.post(
|
|
||||||
self.local_user.shared_inbox,
|
|
||||||
'{"type": "Fish", "object": "exists"}',
|
|
||||||
content_type='application/json')
|
|
||||||
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
|
|
||||||
mock_has_valid.return_value = True
|
|
||||||
self.assertIsInstance(
|
|
||||||
incoming.shared_inbox(request), HttpResponseNotFound)
|
|
||||||
|
|
||||||
def test_inbox_success(self):
|
def test_inbox_success(self):
|
||||||
''' a known type, for which we start a task '''
|
''' a known type, for which we start a task '''
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
|
|
|
@ -4,7 +4,6 @@ from urllib.parse import urldefrag
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -26,10 +25,8 @@ class Inbox(View):
|
||||||
|
|
||||||
# is it valid json? does it at least vaguely resemble an activity?
|
# is it valid json? does it at least vaguely resemble an activity?
|
||||||
try:
|
try:
|
||||||
resp = request.body
|
activity_json = json.loads(request.body)
|
||||||
activity_json = json.loads(resp)
|
except json.decoder.JSONDecodeError:
|
||||||
activity_type = activity_json['type'] # Follow, Accept, Create, etc
|
|
||||||
except (json.decoder.JSONDecodeError, KeyError):
|
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
# verify the signature
|
# verify the signature
|
||||||
|
@ -43,8 +40,7 @@ class Inbox(View):
|
||||||
|
|
||||||
# get the activity dataclass from the type field
|
# get the activity dataclass from the type field
|
||||||
try:
|
try:
|
||||||
serializer = getattr(activitypub, activity_type)
|
activitypub.parse(activity_json)
|
||||||
serializer(**activity_json)
|
|
||||||
except (AttributeError, activitypub.ActivitySerializerError):
|
except (AttributeError, activitypub.ActivitySerializerError):
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue