diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 510f1f3f..ce4acd66 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -2,7 +2,7 @@ import inspect 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 ActivitySerializerError, resolve_remote_id from .image import Image @@ -23,3 +23,7 @@ from .verbs import Add, AddBook, AddListItem, Remove cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) activity_objects = {c[0]: c[1] for c in cls_members \ 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) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 5f35f1d7..596662e8 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -40,6 +40,17 @@ class Signature: signatureValue: str 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) class ActivityObject: @@ -47,13 +58,17 @@ class ActivityObject: id: 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 dataclass, which it ignores. Any field in the dataclass is required or has a default value ''' for field in fields(self): try: + print(field.name, field.type) value = kwargs[field.name] + if field.type == 'ActivityObject' and activity_objects: + value = naive_parse(activity_objects, value) + except KeyError: if field.default == MISSING and \ field.default_factory == MISSING: diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index 01d0c9a3..9be0bb97 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -40,60 +40,6 @@ class Incoming(TestCase): 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): ''' a known type, for which we start a task ''' request = self.factory.post( diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 8f38e7a2..53dd6c40 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -4,7 +4,6 @@ from urllib.parse import urldefrag from django.http import HttpResponse from django.http import HttpResponseBadRequest, HttpResponseNotFound -from django.shortcuts import get_object_or_404 from django.views import View import requests @@ -26,10 +25,8 @@ class Inbox(View): # is it valid json? does it at least vaguely resemble an activity? try: - resp = request.body - activity_json = json.loads(resp) - activity_type = activity_json['type'] # Follow, Accept, Create, etc - except (json.decoder.JSONDecodeError, KeyError): + activity_json = json.loads(request.body) + except json.decoder.JSONDecodeError: return HttpResponseBadRequest() # verify the signature @@ -43,8 +40,7 @@ class Inbox(View): # get the activity dataclass from the type field try: - serializer = getattr(activitypub, activity_type) - serializer(**activity_json) + activitypub.parse(activity_json) except (AttributeError, activitypub.ActivitySerializerError): return HttpResponseNotFound()