Recursively parse activities

This commit is contained in:
Mouse Reeve 2021-02-15 17:23:17 -08:00
parent fd19b55961
commit e810c2bee0
4 changed files with 24 additions and 63 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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(

View file

@ -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()