From bf0897ba817a27224daf2724bba33f26c9cae187 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Mon, 16 Oct 2017 22:21:13 -0700 Subject: [PATCH] activitypub and salmon: return 501 on unsupported activity type --- activitypub.py | 21 ++++++++++++++++----- salmon.py | 24 ++++++++++++++++++++++-- test/test_activitypub.py | 10 ++++++++++ test/test_salmon.py | 24 ++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/activitypub.py b/activitypub.py index b7cb182..c63e5fa 100644 --- a/activitypub.py +++ b/activitypub.py @@ -21,7 +21,15 @@ CONTENT_TYPE_AS = 'application/activity+json' CONNEG_HEADER = { 'Accept': '%s; q=0.9, %s; q=0.8' % (CONTENT_TYPE_AS2, CONTENT_TYPE_AS), } - +SUPPORTED_TYPES = ( + 'Announce', + 'Article', + 'Audio', + 'Image', + 'Like', + 'Note', + 'Video', +) class ActorHandler(webapp2.RequestHandler): """Serves /[DOMAIN], fetches its mf2, converts to AS Actor, and serves it.""" @@ -64,10 +72,13 @@ class InboxHandler(webapp2.RequestHandler): try: activity = json.loads(self.request.body) assert activity - except (TypeError, ValueError, AssertionError): - msg = "Couldn't parse body as JSON" - logging.error(msg, exc_info=True) - common.error(self, msg) + except (TypeError, ValueError, AssertionError) as e: + common.error(self, "Couldn't parse body as JSON: %s" % e) + + type = activity.get('type') + if type not in SUPPORTED_TYPES: + common.error(self, 'Sorry, %s activities are not supported yet.' % type, + status=501) # TODO: verify signature if there is one diff --git a/salmon.py b/salmon.py index 283d669..08f93bb 100644 --- a/salmon.py +++ b/salmon.py @@ -19,6 +19,15 @@ import common ATOM_NS = 'http://www.w3.org/2005/Atom' ATOM_THREADING_NS = 'http://purl.org/syndication/thread/1.0' +SUPPORTED_VERBS = ( + 'checkin', + 'create', + 'like', + 'share', + 'tag', + 'update', +) + class SlapHandler(webapp2.RequestHandler): """Accepts POSTs to /[ACCT]/salmon and converts to outbound webmentions.""" @@ -34,8 +43,19 @@ class SlapHandler(webapp2.RequestHandler): data = utils.decode(parsed['data']) logging.info('Decoded: %s', data) - # verify signature - author = utils.parse_author_uri_from_atom(data) + # check that we support this activity type + try: + activity = atom.atom_to_activity(data) + except ParseError as e: + common.error(self, 'Could not parse envelope data as XML: %s' % e) + + verb = activity.get('verb') + if verb and verb not in SUPPORTED_VERBS: + common.error(self, 'Sorry, %s activities are not supported yet.' % type, + status=501) + + # verify author and signature + author = util.get_url(activity.get('actor')) if ':' not in author: author = 'acct:%s' % author elif not author.startswith('acct:'): diff --git a/test/test_activitypub.py b/test/test_activitypub.py index 957a255..e704ed4 100644 --- a/test/test_activitypub.py +++ b/test/test_activitypub.py @@ -154,3 +154,13 @@ class ActivityPubTest(testutil.TestCase): self.assertEqual('complete', resp.status) as2_like['actor'] = actor self.assertEqual(as2_like, json.loads(resp.source_as2)) + + def test_inbox_unsupported_type(self, mock_get, mock_post): + got = app.get_response('/foo.com/inbox', method='POST', body=json.dumps({ + '@context': ['https://www.w3.org/ns/activitystreams'], + 'id': 'https://xoxo.zone/users/aaronpk#follows/40', + 'type': 'Follow', + 'actor': 'https://xoxo.zone/users/aaronpk', + 'object': 'http://snarfed.org/', + })) + self.assertEquals(501, got.status_int) diff --git a/test/test_salmon.py b/test/test_salmon.py index 8606c15..01f7e08 100644 --- a/test/test_salmon.py +++ b/test/test_salmon.py @@ -24,9 +24,12 @@ import testutil @mock.patch('urllib2.urlopen') class SalmonTest(testutil.TestCase): + def setUp(self): + super(SalmonTest, self).setUp() + self.key = MagicKey.get_or_create('alice') + def send_slap(self, mock_urlopen, mock_get, mock_post, atom_slap): # salmon magic key discovery. first host-meta, then webfinger - self.key = MagicKey.get_or_create('alice') mock_urlopen.side_effect = [ UrlopenResult(200, """\ @@ -131,7 +134,24 @@ class SalmonTest(testutil.TestCase): self.assertEqual('complete', resp.status) self.assertEqual(atom_like, resp.source_atom) - def test_bad_xml(self, mock_urlopen, mock_get, mock_post): + def test_bad_envelope(self, mock_urlopen, mock_get, mock_post): got = app.get_response('/foo.com/salmon', method='POST', body='not xml'.encode('utf-8')) self.assertEquals(400, got.status_int) + + def test_bad_inner_xml(self, mock_urlopen, mock_get, mock_post): + slap = magicsigs.magic_envelope('not xml', common.ATOM_CONTENT_TYPE, self.key) + got = app.get_response('/foo.com/salmon', method='POST', body=slap) + self.assertEquals(400, got.status_int) + + def test_rsvp_not_supported(self, mock_urlopen, mock_get, mock_post): + slap = magicsigs.magic_envelope("""\ + + + https://my/rsvp + http://activitystrea.ms/schema/1.0/rsvp + http://orig/event +""", common.ATOM_CONTENT_TYPE, self.key) + got = app.get_response('/foo.com/salmon', method='POST', body=slap) + self.assertEquals(501, got.status_int)