kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
98465907ff
commit
9a7975bf5d
|
@ -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'
|
||||
|
|
13
models.py
13
models.py
|
@ -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,
|
||||
}))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
||||
|
|
Ładowanie…
Reference in New Issue