activitypub: use proxy mf2 URLs for likes and reposts/boosts

for #4
pull/27/head
Ryan Barrett 2017-10-10 22:42:19 -07:00
rodzic 98465907ff
commit 9a7975bf5d
4 zmienionych plików z 100 dodań i 25 usunięć

Wyświetl plik

@ -22,6 +22,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."""
@ -60,24 +69,35 @@ class InboxHandler(webapp2.RequestHandler):
def post(self, domain):
logging.info('Got: %s', self.request.body)
try:
obj = json.loads(self.request.body)
except (TypeError, ValueError):
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)
self.abort(400, msg)
verb = as2.TYPE_TO_VERB.get(obj.get('type'))
if verb and verb not in ('Create', 'Update'):
common.error(self, '%s activities are not supported yet.' % verb)
type = activity.get('type')
if type not in SUPPORTED_TYPES:
common.error(self, '%s activities are not supported yet.' % type)
# TODO: verify signature if there is one
obj = obj.get('object') or obj
source = obj.get('url')
if not source:
self.abort(400, "Couldn't find original post URL")
source = activity.get('url') or activity.get('id')
obj = activity.get('object')
obj_url = util.get_url(obj)
targets = set(util.get_list(activity, 'inReplyTo'))
if isinstance(obj, dict):
if not source:
source = obj_url or obj.get('id')
targets |= util.get_list(obj, 'inReplyTo')
if not source:
self.abort(400, "Couldn't find source URL or id")
if obj_url:
targets.add(obj_url)
targets = util.get_list(obj, 'inReplyTo') + util.get_list(obj, 'like')
if not targets:
self.abort(400, "Couldn't find target URL (inReplyTo or object)")
@ -85,9 +105,12 @@ class InboxHandler(webapp2.RequestHandler):
for target in targets:
response = Response.get_or_insert(
'%s %s' % (source, target), direction='in', protocol='activitypub',
source_as2=json.dumps(obj))
logging.info('Sending webmention from %s to %s', source, target)
wm = send.WebmentionSend(source, target)
source_as2=json.dumps(activity))
wm_source = (response.proxy_url() if type in ('Like', 'Announce')
else source)
logging.info('Sending webmention from %s to %s', wm_source, target)
wm = send.WebmentionSend(wm_source, target)
if wm.send(headers=common.HEADERS):
logging.info('Success: %s', wm.response)
response.status = 'complete'

Wyświetl plik

@ -2,11 +2,15 @@
Based on webfinger-unofficial/user.py.
"""
import urllib
from Crypto.PublicKey import RSA
from django_salmon import magicsigs
from google.appengine.ext import ndb
from oauth_dropins.webutil.models import StringIdModel
import appengine_config
class MagicKey(StringIdModel):
"""Stores a user's public/private key pair used for Magic Signatures.
@ -77,3 +81,12 @@ class Response(StringIdModel):
created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True)
def proxy_url(self):
"""Returns the Bridgy Fed proxy URL to render this response as HTML."""
if self.source_mf2 or self.source_as2:
source, target = self.key.id().split(' ')
return '%s/render?%s' % (appengine_config.HOST_URL, urllib.urlencode({
'source': source,
'target': target,
}))

Wyświetl plik

@ -78,9 +78,9 @@ class ActivityPubTest(testutil.TestCase):
}
got = app.get_response('/foo.com/inbox', method='POST',
body=json.dumps(as2_note))
self.assertEquals(200, got.status_int, got.body)
mock_get.assert_called_once_with(
'http://orig/post', headers=common.HEADERS, verify=False)
self.assertEquals(200, got.status_int)
expected_headers = copy.deepcopy(common.HEADERS)
expected_headers['Accept'] = '*/*'
@ -100,13 +100,38 @@ class ActivityPubTest(testutil.TestCase):
self.assertEqual('complete', resp.status)
self.assertEqual(as2_note, json.loads(resp.source_as2))
def test_inbox_like_not_supported(self, mock_get, mock_post):
got = app.get_response('/foo.com/inbox', method='POST',
body=json.dumps({
def test_inbox_like_proxy_url(self, mock_get, mock_post):
mock_get.return_value = requests_response(
'<html><head><link rel="webmention" href="/webmention"></html>')
mock_post.return_value = requests_response()
# based on example Mastodon like:
# https://github.com/snarfed/bridgy-fed/issues/4#issuecomment-334212362
# (reposts are very similar)
as2_like = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://this/like',
'type': 'Like',
'object': 'http://orig/post',
}))
self.assertEquals(400, got.status_int)
'actor': 'http://this/author',
}
self.assertIsNone(Response.get_by_id('http://a/reply http://orig/post'))
got = app.get_response('/foo.com/inbox', method='POST',
body=json.dumps(as2_like))
self.assertEquals(200, got.status_int)
mock_get.assert_called_once_with(
'http://orig/post', headers=common.HEADERS, verify=False)
args, kwargs = mock_post.call_args
self.assertEquals(('http://orig/webmention',), args)
self.assertEquals({
# TODO
'source': 'http://localhost/render?source=http%3A%2F%2Fthis%2Flike&target=http%3A%2F%2Forig%2Fpost',
'target': 'http://orig/post',
}, kwargs['data'])
resp = Response.get_by_id('http://this/like http://orig/post')
self.assertEqual('in', resp.direction)
self.assertEqual('activitypub', resp.protocol)
self.assertEqual('complete', resp.status)
self.assertEqual(as2_like, json.loads(resp.source_as2))

Wyświetl plik

@ -2,22 +2,22 @@
"""Unit tests for models.py."""
from __future__ import unicode_literals
import models
from models import MagicKey, Response
import testutil
class ModelsTest(testutil.TestCase):
class MagicKeyTest(testutil.TestCase):
def setUp(self):
super(ModelsTest, self).setUp()
self.key = models.MagicKey.get_or_create('y.z')
super(MagicKeyTest, self).setUp()
self.key = MagicKey.get_or_create('y.z')
def test_magic_key_get_or_create(self):
assert self.key.mod
assert self.key.public_exponent
assert self.key.private_exponent
same = models.MagicKey.get_or_create('y.z')
same = MagicKey.get_or_create('y.z')
self.assertEquals(same, self.key)
def test_href(self):
@ -35,3 +35,17 @@ class ModelsTest(testutil.TestCase):
pem = self.key.private_pem()
self.assertTrue(pem.startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem)
self.assertTrue(pem.endswith('-----END RSA PRIVATE KEY-----'), pem)
class ResponseTest(testutil.TestCase):
def test_proxy_url(self):
resp = Response(id='abc xyz')
self.assertIsNone(resp.proxy_url())
resp.source_atom = 'atom'
self.assertIsNone(resp.proxy_url())
resp.source_as2 = 'as2'
self.assertEquals('http://localhost/render?source=abc&target=xyz',
resp.proxy_url())