flask: port webmention, add exception handler

flask
Ryan Barrett 2021-07-11 08:48:28 -07:00
rodzic c17cb3394b
commit 4fffc073d2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
6 zmienionych plików z 181 dodań i 181 usunięć

10
app.py
Wyświetl plik

@ -16,4 +16,14 @@ app.wsgi_app = handlers.ndb_context_middleware(
cache = Cache(app)
@app.errorhandler(Exception)
def handle_exception(e):
"""A Flask error handler that propagates HTTP exceptions into the response."""
code, body = util.interpret_http_exception(e)
if code:
return ((f'Upstream server request failed: {e}' if code in ('502', '504')
else f'HTTP Error {code}: {body}'),
int(code))
return e
import activitypub, add_webmention, logs, redirect, render, salmon, superfeedr, webfinger, webmention

Wyświetl plik

@ -152,11 +152,11 @@ def content_type(resp):
return type.split(';')[0]
def get_required_param(request, name):
def get_required_param(name):
try:
val = request.args.get(name)
val = request.args.get(name) or request.form.get(name)
except (UnicodeDecodeError, UnicodeEncodeError) as e:
abort(400, f"Couldn't decode query parameters as UTF-8: {e}")
abort(400, f"Couldn't decode parameters as UTF-8: {e}")
if not val:
abort(400, f'Missing required parameter: {name}')

Wyświetl plik

@ -18,8 +18,8 @@ CACHE_TIME = datetime.timedelta(minutes=15)
response_filter=common.not_5xx)
def render():
"""Fetches a stored Response and renders it as HTML."""
source = common.get_required_param(request, 'source')
target = common.get_required_param(request, 'target')
source = common.get_required_param('source')
target = common.get_required_param('target')
id = f'{source} {target}'
resp = Response.get_by_id(id)

Wyświetl plik

@ -64,13 +64,16 @@ REPOST_AS2 = {
},
}
client = app.test_client()
@mock.patch('requests.post')
@mock.patch('requests.get')
class WebmentionTest(testutil.TestCase):
def setUp(self):
super(WebmentionTest, self).setUp()
self.key = MagicKey.get_or_create('a')
app.testing = True
self.orig_html_as2 = requests_response("""\
<html>
@ -276,13 +279,12 @@ class WebmentionTest(testutil.TestCase):
return env['data']
def test_bad_source_url(self, mock_get, mock_post):
got = application.get_response('/webmention', method='POST', body=b'')
self.assertEqual(400, got.status_int)
got = client.post('/webmention', data=b'')
self.assertEqual(400, got.status_code)
mock_get.side_effect = ValueError('foo bar')
got = application.get_response('/webmention', method='POST',
body=urlencode({'source': 'bad'}).encode())
self.assertEqual(400, got.status_int)
got = client.post('/webmention', data={'source': 'bad'})
self.assertEqual(400, got.status_code)
def test_no_source_entry(self, mock_get, mock_post):
mock_get.return_value = requests_response("""
@ -292,12 +294,11 @@ class WebmentionTest(testutil.TestCase):
</body>
</html>""", content_type=CONTENT_TYPE_HTML)
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(400, got.status_int)
got = client.post( '/webmention', data={
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(400, got.status_code)
mock_get.assert_has_calls((self.req('http://a/post'),))
@ -309,12 +310,11 @@ class WebmentionTest(testutil.TestCase):
</body>
</html>""", content_type=CONTENT_TYPE_HTML)
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((self.req('http://a/post'),))
@ -324,43 +324,36 @@ class WebmentionTest(testutil.TestCase):
content_type=CONTENT_TYPE_HTML),
ValueError('foo bar'))
got = application.get_response(
'/webmention', method='POST',
body=urlencode({'source': 'http://a/post'}).encode())
self.assertEqual(400, got.status_int)
got = client.post('/webmention', data={'source': 'http://a/post'})
self.assertEqual(400, got.status_code)
def test_target_fetch_fails(self, mock_get, mock_post):
def test_source_fetch_fails(self, mock_get, mock_post):
mock_get.side_effect = (
requests_response(self.reply_html.replace('http://orig/post', 'bad'),
content_type=CONTENT_TYPE_HTML),
requests.Timeout('foo bar'))
got = application.get_response(
'/webmention', method='POST',
body=urlencode({'source': 'http://a/post'}).encode())
self.assertEqual(502, got.status_int)
got = client.post('/webmention', data={'source': 'http://a/post'})
self.assertEqual(502, got.status_code)
def test_target_fetch_has_no_content_type(self, mock_get, mock_post):
def test_source_fetch_has_no_content_type(self, mock_get, mock_post):
mock_get.side_effect = (
requests_response(self.reply_html),
requests_response(self.reply_html, content_type='None')
)
got = application.get_response(
'/webmention', method='POST',
body=urlencode({'source': 'http://a/post'}).encode())
self.assertEqual(502, got.status_int)
got = client.post('/webmention', data={'source': 'http://a/post'})
self.assertEqual(502, got.status_code)
def test_no_backlink(self, mock_get, mock_post):
mock_get.return_value = requests_response(
self.reply_html.replace('<a href="http://localhost/"></a>', ''),
content_type=CONTENT_TYPE_HTML)
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(400, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/post',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(400, got.status_code)
mock_get.assert_has_calls((self.req('http://a/post'),))
@ -368,12 +361,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = self.activitypub_gets
mock_post.return_value = requests_response('abc xyz', status=203)
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(203, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(203, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/reply'),
@ -410,12 +402,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = self.activitypub_gets
mock_post.return_value = requests_response('abc xyz')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
@ -438,12 +429,11 @@ class WebmentionTest(testutil.TestCase):
self.actor]
mock_post.return_value = requests_response('abc xyz', status=203)
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(203, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(203, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/reply'),
@ -462,12 +452,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = self.activitypub_gets
mock_post.return_value = requests_response('abc xyz')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
@ -477,12 +466,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.repost, self.orig_as2, self.actor]
mock_post.return_value = requests_response('abc xyz')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/repost'),
@ -511,12 +499,11 @@ class WebmentionTest(testutil.TestCase):
self.orig_html_as2, self.orig_as2, self.actor]
mock_post.return_value = requests_response('abc xyz')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/reply'),
@ -544,11 +531,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [missing_url, self.orig_as2, self.actor]
mock_post.return_value = requests_response('abc xyz', status=203)
got = application.get_response('/webmention', method='POST', body=urlencode({
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(203, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(203, got.status_code)
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
@ -579,11 +566,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [repost, author, self.orig_as2, self.actor]
mock_post.return_value = requests_response('abc xyz', status=201)
got = application.get_response('/webmention', method='POST', body=urlencode({
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(201, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/repost',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(201, got.status_code)
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
@ -624,12 +611,11 @@ class WebmentionTest(testutil.TestCase):
'inbox': 'https://inbox',
}}))
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://orig/post',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://orig/post',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://orig/post'),
@ -661,12 +647,11 @@ class WebmentionTest(testutil.TestCase):
'orig', 'https://mastodon/aaa',
last_follow=json_dumps({'actor': {'inbox': 'https://inbox'}}))
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://orig/post',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://orig/post',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
self.assertEqual(('https://inbox',), mock_post.call_args[0])
create = copy.deepcopy(self.create_as2)
@ -680,12 +665,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.follow, self.actor]
mock_post.return_value = requests_response('abc xyz')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/follow',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/follow',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/follow'),
@ -713,15 +697,15 @@ class WebmentionTest(testutil.TestCase):
mock_post.return_value = requests_response(
'abc xyz', status=405, url='https://foo.com/inbox')
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/follow',
'target': 'https://fed.brid.gy/',
}).encode())
self.assertEqual(502, got.status_int, got.text)
got = client.post('/webmention', data={
'source': 'http://a/follow',
'target': 'https://fed.brid.gy/',
})
body = got.get_data(as_text=True)
self.assertEqual(502, got.status_code, body)
self.assertEqual(
'405 Client Error: None for url: https://foo.com/inbox ; abc xyz',
got.text)
body)
mock_get.assert_has_calls((
self.req('http://a/follow'),
@ -748,12 +732,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.reply, self.not_fediverse,
self.orig_html_atom, self.orig_atom]
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/reply',
'target': 'http://orig/post',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'http://orig/post',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/reply'),
@ -792,12 +775,11 @@ class WebmentionTest(testutil.TestCase):
def test_salmon_like(self, mock_get, mock_post):
mock_get.side_effect = [self.like, self.orig_html_atom, self.orig_atom]
got = application.get_response(
'/webmention', method='POST', body=urlencode({
'source': 'http://a/like',
'target': 'http://orig/post',
}).encode())
self.assertEqual(200, got.status_int)
got = client.post('/webmention', data={
'source': 'http://a/like',
'target': 'http://orig/post',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/like'),
@ -844,11 +826,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.reply, self.not_fediverse,
self.orig_html_atom, orig_atom, webfinger]
got = application.get_response('/webmention', method='POST', body=urlencode({
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'http://orig/post',
}).encode())
self.assertEqual(200, got.status_int)
})
self.assertEqual(200, got.status_code)
mock_get.assert_any_call(
'http://orig/.well-known/webfinger?resource=acct:ryan@orig',
@ -862,12 +844,13 @@ class WebmentionTest(testutil.TestCase):
</html>""", 'http://orig/url')
mock_get.side_effect = [self.reply, self.not_fediverse, orig_no_atom]
got = application.get_response('/webmention', method='POST', body=urlencode({
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'http://orig/post',
}).encode())
self.assertEqual(400, got.status_int)
self.assertIn('Target post http://orig/url has no Atom link', got.body.decode())
})
self.assertEqual(400, got.status_code)
self.assertIn('Target post http://orig/url has no Atom link',
got.get_data(as_text=True))
self.assertIsNone(Response.get_by_id('http://a/reply http://orig/post'))
@ -881,11 +864,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.reply, self.not_fediverse, orig_relative,
self.orig_atom]
got = application.get_response('/webmention', method='POST', body=urlencode({
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'http://orig/post',
}).encode())
self.assertEqual(200, got.status_int)
})
self.assertEqual(200, got.status_code)
mock_get.assert_any_call('http://orig/atom/1', headers=HEADERS,
stream=True, timeout=util.HTTP_TIMEOUT)
@ -902,11 +885,11 @@ class WebmentionTest(testutil.TestCase):
mock_get.side_effect = [self.reply, self.not_fediverse, orig_base,
self.orig_atom]
got = application.get_response('/webmention', method='POST', body=urlencode({
got = client.post('/webmention', data={
'source': 'http://a/reply',
'target': 'http://orig/post',
}).encode())
self.assertEqual(200, got.status_int)
})
self.assertEqual(200, got.status_code)
mock_get.assert_any_call('http://orig/base/atom/1', headers=HEADERS,
stream=True, timeout=util.HTTP_TIMEOUT)

Wyświetl plik

@ -155,7 +155,7 @@ class WebfingerHandler(UserHandler):
https://tools.ietf.org/html/rfc7033#section-4
"""
def template_vars(self):
resource = util.get_required_param(self, 'resource')
resource = common.get_required_param('resource')
try:
user, domain = util.parse_acct_uri(resource)
if domain in common.DOMAINS:

Wyświetl plik

@ -6,11 +6,13 @@ TODO tests:
"""
import logging
import urllib.parse
from urllib.parse import urlencode
from urllib.parse import urlencode
import django_salmon
from django_salmon import magicsigs
import feedparser
from flask import request
from flask.views import View
from google.cloud.ndb import Key
from granary import as2, atom, microformats2, source
import mf2util
@ -21,13 +23,15 @@ import webapp2
from webob import exc
import activitypub
from app import app
import common
from common import error
from models import Follower, MagicKey, Response
SKIP_EMAIL_DOMAINS = frozenset(('localhost', 'snarfed.org'))
class WebmentionHandler():
class Webmention(View):
"""Handles inbound webmention, converts to ActivityPub or Salmon."""
source_url = None # string
source_domain = None # string
@ -35,11 +39,11 @@ class WebmentionHandler():
source_obj = None # parsed AS1 dict
target_resp = None # requests.Response
def post(self):
logging.info('Params: %s', list(self.request.params.items()))
def dispatch_request(self):
logging.info(f'Params: {list(request.form.items())}')
# fetch source page
source = util.get_required_param(self, 'source')
source = common.get_required_param('source')
source_resp = common.requests_get(source)
self.source_url = source_resp.url or source
self.source_domain = urllib.parse.urlparse(self.source_url).netloc.split(':')[0]
@ -49,16 +53,16 @@ class WebmentionHandler():
# check for backlink to bridgy fed (for webmention spec and to confirm
# source's intent to federate to mastodon)
if (self.request.host_url not in source_resp.text and
urllib.parse.quote(self.request.host_url, safe='') not in source_resp.text):
self.error("Couldn't find link to {request.host_url}")
if (request.host_url not in source_resp.text and
urllib.parse.quote(request.host_url, safe='') not in source_resp.text):
return error("Couldn't find link to {request.host_url}")
# convert source page to ActivityStreams
entry = mf2util.find_first_entry(self.source_mf2, ['h-entry'])
if not entry:
self.error('No microformats2 found on %s' % self.source_url)
return error('No microformats2 found on {self.source_url}')
logging.info('First entry: %s', json_dumps(entry, indent=2))
logging.info(f'First entry: {json_dumps(entry, indent=2)}')
# make sure it has url, since we use that for AS2 id, which is required
# for ActivityPub.
props = entry.setdefault('properties', {})
@ -66,17 +70,20 @@ class WebmentionHandler():
props['url'] = [self.source_url]
self.source_obj = microformats2.json_to_object(entry, fetch_mf2=True)
logging.info('Converted to AS1: %s', json_dumps(self.source_obj, indent=2))
logging.info(f'Converted to AS1: {json_dumps(self.source_obj, indent=2)}')
tried_ap = self.try_activitypub()
if tried_ap is None:
self.try_salmon()
for method in self.try_activitypub, self.try_salmon:
ret = method()
if ret:
return ret
return ''
def try_activitypub(self):
"""Attempts ActivityPub delivery.
Returns True if we succeeded, False if we failed, None if ActivityPub
was not available.
Returns Flask response (string body or tuple) if we succeeded or failed,
None if ActivityPub was not available.
"""
targets = self._activitypub_targets()
if not targets:
@ -90,7 +97,7 @@ class WebmentionHandler():
for resp, inbox in targets:
target_obj = json_loads(resp.target_as2) if resp.target_as2 else None
source_activity = self.postprocess_as2(
source_activity = common.postprocess_as2(
as2.from_as1(self.source_obj), target=target_obj, key=key)
if resp.status == 'complete':
@ -108,15 +115,11 @@ class WebmentionHandler():
# Pass the AP response status code and body through as our response
if last_success:
self.response.status_int = last_success.status_code
self.response.write(last_success.text)
return last_success.text, last_success.status_code
elif isinstance(error, (requests.HTTPError, exc.HTTPBadGateway)):
self.response.status_int = error.status_code
self.response.write(str(error))
return str(error), error.status_code
else:
self.response.write(str(error))
return bool(last_success)
return str(error)
def _targets(self):
"""
@ -188,9 +191,9 @@ class WebmentionHandler():
inbox_url = actor.get('inbox')
actor = actor.get('url') or actor.get('id')
if not inbox_url and not actor:
self.error('Target object has no actor or attributedTo with URL or id.')
return error('Target object has no actor or attributedTo with URL or id.')
elif not isinstance(actor, str):
self.error('Target actor or attributedTo has unexpected url or id object: %r' % actor)
return error(f'Target actor or attributedTo has unexpected url or id object: {actor}')
if not inbox_url:
# fetch actor as AS object
@ -200,7 +203,7 @@ class WebmentionHandler():
if not inbox_url:
# TODO: probably need a way to save errors like this so that we can
# return them if ostatus fails too.
# self.error('Target actor has no inbox')
# return error('Target actor has no inbox')
continue
inbox_url = urllib.parse.urljoin(target_url, inbox_url)
@ -209,7 +212,11 @@ class WebmentionHandler():
return resps_and_inbox_urls
def try_salmon(self):
"""Returns True if we attempted OStatus delivery. Raises otherwise."""
"""
Returns Flask response (string body or tuple) if we attempted OStatus
delivery (whether successful or not), None if we didn't attempt, raises
an exception otherwise.
"""
target = None
if self.target_resp:
target = self.target_resp.url
@ -219,7 +226,7 @@ class WebmentionHandler():
target = targets[0]
if not target:
logging.warning("No targets or followers. Ignoring.")
return False
return
resp = Response.get_or_create(
source=self.source_url, target=target, direction='out',
@ -249,7 +256,7 @@ class WebmentionHandler():
parsed = util.parse_html(self.target_resp)
atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
if not atom_url or not atom_url.get('href'):
self.error('Target post %s has no Atom link' % resp.target(), status=400)
return error(f'Target post {resp.target()} has no Atom link')
# fetch Atom target post, extract and inject id into source object
base_url = ''
@ -262,7 +269,7 @@ class WebmentionHandler():
feed = common.requests_get(atom_url).text
parsed = feedparser.parse(feed)
logging.info('Parsed: %s', json_dumps(parsed, indent=2))
logging.info(f'Parsed: {json_dumps(parsed, indent=2)}')
entry = parsed.entries[0]
target_id = entry.id
in_reply_to = self.source_obj.get('inReplyTo')
@ -285,7 +292,7 @@ class WebmentionHandler():
self.source_obj.setdefault('tags', []).append({'url': url})
# extract and discover salmon endpoint
logging.info('Discovering Salmon endpoint in %s', atom_url)
logging.info(f'Discovering Salmon endpoint in {atom_url}')
endpoint = django_salmon.discover_salmon_endpoint(feed)
if not endpoint:
@ -305,8 +312,8 @@ class WebmentionHandler():
pass
if not endpoint:
self.error('No salmon endpoint found!', status=400)
logging.info('Discovered Salmon endpoint %s', endpoint)
return error('No salmon endpoint found!')
logging.info(f'Discovered Salmon endpoint {endpoint}')
# construct reply Atom object
self.source_url = resp.source()
@ -314,22 +321,22 @@ class WebmentionHandler():
if self.source_obj.get('verb') not in source.VERBS_WITH_OBJECT:
activity = {'object': self.source_obj}
entry = atom.activity_to_atom(activity, xml_base=self.source_url)
logging.info('Converted %s to Atom:\n%s', self.source_url, entry)
logging.info(f'Converted {self.source_url} to Atom:\n{entry}')
# sign reply and wrap in magic envelope
domain = urllib.parse.urlparse(self.source_url).netloc
key = MagicKey.get_or_create(domain)
logging.info('Using key for %s: %s', domain, key)
logging.info(f'Using key for {domain}: {key}')
magic_envelope = magicsigs.magic_envelope(
entry, common.CONTENT_TYPE_ATOM, key).decode()
logging.info('Sending Salmon slap to %s', endpoint)
logging.info(f'Sending Salmon slap to {endpoint}')
common.requests_post(
endpoint, data=common.XML_UTF8 + magic_envelope,
headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE})
return True
return ''
ROUTES = [
('/webmention', WebmentionHandler),
]
app.add_url_rule('/webmention', view_func=Webmention.as_view('webmention'),
methods=['POST'])