diff --git a/activitypub.py b/activitypub.py index bc884f5..bf69c52 100644 --- a/activitypub.py +++ b/activitypub.py @@ -14,13 +14,6 @@ import webapp2 import common from models import MagicKey - -# https://www.w3.org/TR/activitypub/#retrieving-objects -CONTENT_TYPE_AS2 = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' -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', @@ -31,6 +24,7 @@ SUPPORTED_TYPES = ( 'Video', ) + class ActorHandler(webapp2.RequestHandler): """Serves /[DOMAIN], fetches its mf2, converts to AS Actor, and serves it.""" @@ -56,7 +50,7 @@ representative h-card on %s""" % resp.url) logging.info('Returning: %s', json.dumps(obj, indent=2)) self.response.headers.update({ - 'Content-Type': CONTENT_TYPE_AS2, + 'Content-Type': common.CONTENT_TYPE_AS2, 'Access-Control-Allow-Origin': '*', }) self.response.write(json.dumps(obj, indent=2)) @@ -86,8 +80,7 @@ class InboxHandler(webapp2.RequestHandler): if activity.get('type') in ('Like', 'Announce'): actor = activity.get('actor') if actor: - activity['actor'] = common.requests_get( - actor, parse_json=True, headers=CONNEG_HEADER) + activity['actor'] = common.get_as2(actor) # send webmentions to each target as1 = as2.to_as1(activity) diff --git a/common.py b/common.py index 8673376..7b6a45c 100644 --- a/common.py +++ b/common.py @@ -95,7 +95,7 @@ def get_as2(url): url: string Returns: - requests.Response + dict, AS2 object parsed from JSON Raises: requests.HTTPError, webob.exc.HTTPException @@ -107,7 +107,7 @@ def get_as2(url): resp = requests_get(url, headers=CONNEG_HEADERS_AS2_HTML) if resp.headers.get('Content-Type') in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD): - return resp + return resp.json() parsed = BeautifulSoup(resp.content, from_encoding=resp.encoding) as2 = parsed.find('link', rel=('alternate', 'self'), type=( @@ -118,7 +118,7 @@ def get_as2(url): resp = requests_get(urlparse.urljoin(resp.url, as2['href']), headers=CONNEG_HEADERS_AS2) if resp.headers.get('Content-Type') in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD): - return resp + return resp.json() _error() diff --git a/test/test_activitypub.py b/test/test_activitypub.py index e704ed4..30590fb 100644 --- a/test/test_activitypub.py +++ b/test/test_activitypub.py @@ -35,7 +35,7 @@ class ActivityPubTest(testutil.TestCase): mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT) self.assertEquals(200, got.status_int) - self.assertEquals(activitypub.CONTENT_TYPE_AS2, got.headers['Content-Type']) + self.assertEquals(common.CONTENT_TYPE_AS2, got.headers['Content-Type']) self.assertEquals({ '@context': 'https://www.w3.org/ns/activitystreams', 'type' : 'Person', @@ -111,7 +111,7 @@ class ActivityPubTest(testutil.TestCase): } mock_get.side_effect = [ # source actor - requests_response(actor), + requests_response(actor, headers={'Content-Type': common.CONTENT_TYPE_AS2}), # target post webmention discovery requests_response( ''), @@ -134,7 +134,7 @@ class ActivityPubTest(testutil.TestCase): self.assertEquals(200, got.status_int) as2_headers = copy.deepcopy(common.HEADERS) - as2_headers.update(activitypub.CONNEG_HEADER) + as2_headers.update(common.CONNEG_HEADERS_AS2_HTML) mock_get.assert_has_calls(( call('http://orig/actor', headers=as2_headers, timeout=15), call('http://orig/post', headers=common.HEADERS, verify=False), diff --git a/test/test_common.py b/test/test_common.py index 9b6c9e0..45db5d5 100644 --- a/test/test_common.py +++ b/test/test_common.py @@ -23,7 +23,8 @@ HTML_WITH_AS2 = requests_response("""\ """, headers={ 'Content-Type': common.CONTENT_TYPE_HTML, }) -AS2 = requests_response({}, headers={ +AS2_OBJ = {'foo': ['bar']} +AS2 = requests_response(AS2_OBJ, headers={ 'Content-Type': common.CONTENT_TYPE_AS2, }) NOT_ACCEPTABLE = requests_response(status=406) @@ -34,7 +35,7 @@ class CommonTest(testutil.TestCase): @mock.patch('requests.get', return_value=AS2) def test_get_as2_direct(self, mock_get): resp = common.get_as2('http://orig') - self.assertEqual(AS2, resp) + self.assertEqual(AS2_OBJ, resp) mock_get.assert_has_calls(( self.req('http://orig', headers=common.CONNEG_HEADERS_AS2_HTML), )) @@ -42,7 +43,7 @@ class CommonTest(testutil.TestCase): @mock.patch('requests.get', side_effect=[HTML_WITH_AS2, AS2]) def test_get_as2_via_html(self, mock_get): resp = common.get_as2('http://orig') - self.assertEqual(AS2, resp) + self.assertEqual(AS2_OBJ, resp) mock_get.assert_has_calls(( self.req('http://orig', headers=common.CONNEG_HEADERS_AS2_HTML), self.req('http://as2', headers=common.CONNEG_HEADERS_AS2), diff --git a/test/testutil.py b/test/testutil.py index ad8ca9f..7b5c92f 100644 --- a/test/testutil.py +++ b/test/testutil.py @@ -1,11 +1,14 @@ """Common test utility code. """ +import copy import unittest from google.appengine.datastore import datastore_stub_util from google.appengine.ext import testbed +from mock import call +from oauth_dropins.webutil import testutil, util -from oauth_dropins.webutil import testutil +import common class TestCase(unittest.TestCase, testutil.Asserts): @@ -24,3 +27,11 @@ class TestCase(unittest.TestCase, testutil.Asserts): def tearDown(self): self.testbed.deactivate() super(TestCase, self).tearDown() + + def req(self, url, **kwargs): + """Returns a mock requests call.""" + headers = copy.deepcopy(common.HEADERS) + headers.update(kwargs.get('headers', {})) + kwargs['headers'] = headers + kwargs.setdefault('timeout', util.HTTP_TIMEOUT) + return call(url, **kwargs)