mastodon interop: webmention cleanup, tests

mastodon
Ryan Barrett 2017-09-03 15:26:41 -07:00
rodzic 4dd8f7e1a7
commit fb977c5f52
2 zmienionych plików z 42 dodań i 30 usunięć

Wyświetl plik

@ -6,11 +6,14 @@ TODO: test error handling
from __future__ import unicode_literals
import copy
import json
import logging
import urllib
import urllib2
from django_salmon import magicsigs, utils
import feedparser
from granary import atom, microformats2
import mf2py
import mock
from mock import call
from oauth_dropins.webutil import util
@ -19,7 +22,7 @@ import requests
import activitypub
import common
import models
from models import MagicKey
import testutil
import webmention
from webmention import app
@ -37,9 +40,9 @@ class WebmentionTest(testutil.TestCase):
<link href='http://orig/atom' rel='alternate' type='application/atom+xml'>
</meta>
</html>
""", content_type='text/html; charset=utf-8')
""", url='http://orig/post', content_type='text/html; charset=utf-8')
self.reply = requests_response("""\
self.reply_html = """\
<html>
<body>
<div class="h-entry">
@ -50,7 +53,11 @@ class WebmentionTest(testutil.TestCase):
</div>
</body>
</html>
""", content_type='text/html; charset=utf-8')
"""
self.reply = requests_response(
self.reply_html, content_type='text/html; charset=utf-8')
mf2 = mf2py.parse(self.reply_html, url='http://a/reply')
self.reply_obj = microformats2.json_to_object(mf2['items'][0])
def test_activitypub(self, mock_get, mock_post):
article = requests_response({
@ -103,7 +110,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(expected_headers, kwargs['headers'])
def test_salmon(self, mock_get, mock_post):
atom = requests_response("""\
orig_atom = requests_response("""\
<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<id>tag:fed.brid.gy,2017-08-22:orig-post</id>
@ -111,7 +118,7 @@ class WebmentionTest(testutil.TestCase):
<content type="html">baz baj</content>
</entry>
""")
mock_get.side_effect = [self.reply, self.orig, atom]
mock_get.side_effect = [self.reply, self.orig, orig_atom]
got = app.get_response(
'/webmention', method='POST', body=urllib.urlencode({
@ -132,10 +139,14 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(common.MAGIC_ENVELOPE_CONTENT_TYPE,
kwargs['headers']['Content-Type'])
envelope = utils.parse_magic_envelope(kwargs['data'])
assert envelope['sig']
env = utils.parse_magic_envelope(kwargs['data'])
self.reply_obj['inReplyTo'][0]['id'] = 'tag:fed.brid.gy,2017-08-22:orig-post'
reply_atom = atom.activity_to_atom(
{'object': self.reply_obj}, xml_base='http://a/reply')
key = MagicKey.get_by_id('@a')
assert magicsigs.verify(None, reply_atom, env['sig'], key=key)
data = utils.decode(envelope['data'])
data = utils.decode(env['data'])
parsed = feedparser.parse(data)
entry = parsed.entries[0]
@ -155,7 +166,7 @@ class WebmentionTest(testutil.TestCase):
entry.content[0]['value'])
def test_salmon_get_salmon_from_webfinger(self, mock_get, mock_post):
atom = requests_response("""\
orig_atom = requests_response("""\
<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<author>
@ -172,7 +183,7 @@ class WebmentionTest(testutil.TestCase):
'href': 'http://orig/@ryan/salmon',
}],
})
mock_get.side_effect = [self.reply, self.orig, atom, webfinger]
mock_get.side_effect = [self.reply, self.orig, orig_atom, webfinger]
got = app.get_response('/webmention', method='POST', body=urllib.urlencode({
'source': 'http://a/reply',

Wyświetl plik

@ -47,13 +47,10 @@ class WebmentionHandler(webapp2.RequestHandler):
source_obj = microformats2.json_to_object(entry)
logging.info('Converted to AS: %s', json.dumps(source_obj, indent=2))
return self.send_salmon(source_obj, target_url=target)
# fetch target page as AS object
try:
resp = common.requests_get(target, headers=activitypub.CONNEG_HEADER,
log=True)
target_obj = resp.json()
except requests.HTTPError as e:
if e.response.status_code // 100 == 4:
return self.send_salmon(source_obj, target_url=target)
@ -62,26 +59,15 @@ class WebmentionHandler(webapp2.RequestHandler):
if resp.headers.get('Content-Type').startswith('text/html'):
return self.send_salmon(source_obj, target_resp=resp)
# post-process AS1 to look enough like AS2 to work
in_reply_tos = util.get_list(source_obj, 'inReplyTo')
if in_reply_tos:
source_obj['inReplyTo'] = in_reply_tos[0]['url']
if len(in_reply_tos) > 1:
logging.warning("AS2 doesn't support multiple inReplyTo URLs! "
'Only using the first: %s' % source_obj['inReplyTo'])
source_obj.setdefault('cc', []).extend([
activitypub.PUBLIC_AUDIENCE,
source_obj['inReplyTo'],
])
# find actor's inbox
target_obj = resp.json()
inbox_url = target_obj.get('inbox')
if not inbox_url:
# fetch actor as AS object
actor_url = target_obj.get('actor') or target_obj.get('attributedTo')
if isinstance(actor_url, dict):
actor_url = actor.get('url')
actor_url = actor_url.get('url')
if not actor_url:
self.abort(400, 'Target object has no actor or attributedTo URL')
@ -95,6 +81,18 @@ class WebmentionHandler(webapp2.RequestHandler):
# self.abort(400, 'Target actor has no inbox')
return self.send_salmon(source_obj, target_url=target)
# post-process AS1 to look enough like AS2 to work
in_reply_tos = util.get_list(source_obj, 'inReplyTo')
if in_reply_tos:
source_obj['inReplyTo'] = in_reply_tos[0]['url']
if len(in_reply_tos) > 1:
logging.warning("AS2 doesn't support multiple inReplyTo URLs! "
'Only using the first: %s' % source_obj['inReplyTo'])
source_obj.setdefault('cc', []).extend([
activitypub.PUBLIC_AUDIENCE,
source_obj['inReplyTo'],
])
# deliver source object to target actor's inbox
resp = common.requests_post(
urlparse.urljoin(target, inbox_url), json=source_obj,
@ -128,8 +126,9 @@ class WebmentionHandler(webapp2.RequestHandler):
# original post's author to make it show up as a reply:
# app/services/process_interaction_service.rb
# ...so add them as a tag, which atom renders as a rel-mention link.
if entry.authors:
url = entry.authors[0].href
authors = entry.get('authors', None)
if authors:
url = entry.authors[0].get('href')
if url:
source_obj.setdefault('tags', []).append({'url': url})
@ -161,8 +160,10 @@ class WebmentionHandler(webapp2.RequestHandler):
# sign reply and wrap in magic envelope
# TODO: use author h-card's u-url?
# TODO: person emoji username
# BETTER: TODO: extract u-nickname or first name
domain = urlparse.urlparse(source_url).netloc.split(':')[0]
key = models.MagicKey.get_or_create(domain)
key = models.MagicKey.get_or_create('@' + domain)
magic_envelope = magicsigs.magic_envelope(
entry, common.ATOM_CONTENT_TYPE, key)