From 4dd8f7e1a701f07f18520cf2ad507b43ac28fd34 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Sun, 3 Sep 2017 12:54:10 -0700 Subject: [PATCH] mastodon interop: salmon + activitypub + webfinger cleanup, tests --- activitypub.py | 4 ++-- app.yaml | 2 +- common.py | 3 ++- salmon.py | 14 ++++++++------ test/test_salmon.py | 4 ++-- test/test_webfinger.py | 16 ++++++++-------- webfinger.py | 13 ++++++------- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/activitypub.py b/activitypub.py index 404896c..db66293 100644 --- a/activitypub.py +++ b/activitypub.py @@ -89,6 +89,6 @@ class InboxHandler(webapp2.RequestHandler): app = webapp2.WSGIApplication([ - (r'/(%s)/?' % common.DOMAIN_RE, ActorHandler), - (r'/(%s)/inbox' % common.DOMAIN_RE, InboxHandler), + (r'/%s/?' % common.DOMAIN_RE, ActorHandler), + (r'/%s/inbox' % common.DOMAIN_RE, InboxHandler), ], debug=appengine_config.DEBUG) diff --git a/app.yaml b/app.yaml index d1b8b5b..c9c3c42 100644 --- a/app.yaml +++ b/app.yaml @@ -74,7 +74,7 @@ handlers: script: salmon.app secure: always -- url: /(acct:)?@[^/]+/? +- url: /(acct:)?([^@]+)@@[^/]+/? script: webfinger.app secure: always diff --git a/common.py b/common.py index 71661e8..ccacfc0 100644 --- a/common.py +++ b/common.py @@ -6,7 +6,8 @@ from oauth_dropins.webutil import util import requests from webob import exc -DOMAIN_RE = r'[^/]+\.[^/]+' +DOMAIN_RE = r'([^/]+\.[^/]+)' +ACCT_RE = r'(?:acct:)?([^@]+)@' + DOMAIN_RE HEADERS = { 'User-Agent': 'Bridgy Fed (https://fed.brid.gy/)', } diff --git a/salmon.py b/salmon.py index dbdf0a5..aabc67d 100644 --- a/salmon.py +++ b/salmon.py @@ -21,10 +21,10 @@ ATOM_THREADING_NS = 'http://purl.org/syndication/thread/1.0' class SlapHandler(webapp2.RequestHandler): - """Accepts POSTs to /[DOMAIN]/salmon and converts to outbound webmentions.""" + """Accepts POSTs to /[ACCT]/salmon and converts to outbound webmentions.""" # TODO: unify with activitypub - def post(self, domain): + def post(self, username, domain): logging.info('Got: %s', self.request.body) parsed = utils.parse_magic_envelope(self.request.body) @@ -39,12 +39,14 @@ class SlapHandler(webapp2.RequestHandler): common.error(self, 'Author URI %s has unsupported scheme; expected acct:' % author) logging.info('Fetching Salmon key for %s' % author) - if not magicsigs.verify(data, parsed['sig'], author_uri=author): + if not magicsigs.verify(author, data, parsed['sig']): common.error(self, 'Could not verify magic signature.') logging.info('Verified magic signature.') - # Mastodon doesn't do this! so screw it. - # # verify that the timestamp is recent (required by spec) + # Verify that the timestamp is recent. Required by spec. + # I get that this helps prevent spam, but in practice it's a bit silly, + # and other major implementations don't (e.g. Mastodon), so forget it. + # # updated = utils.parse_updated_from_atom(data) # if not utils.verify_timestamp(updated): # common.error(self, 'Timestamp is more than 1h old.') @@ -82,5 +84,5 @@ class SlapHandler(webapp2.RequestHandler): app = webapp2.WSGIApplication([ - (r'/(?:acct:)?@%s/salmon' % common.DOMAIN_RE, SlapHandler), + (r'/%s/salmon' % common.ACCT_RE, SlapHandler), ], debug=appengine_config.DEBUG) diff --git a/test/test_salmon.py b/test/test_salmon.py index 053b71f..353a5ae 100644 --- a/test/test_salmon.py +++ b/test/test_salmon.py @@ -64,8 +64,8 @@ class SalmonTest(testutil.TestCase): My Reply %s """ % datetime.datetime.now().isoformat(b'T') - slap = magicsigs.magic_envelope(atom_reply, 'application/atom+xml', key) - got = app.get_response('/@foo.com/salmon', method='POST', body=slap) + slap = magicsigs.magic_envelope(atom_reply, common.ATOM_CONTENT_TYPE, key) + got = app.get_response('/me@foo.com/salmon', method='POST', body=slap) self.assertEquals(200, got.status_int) # check salmon magic key discovery diff --git a/test/test_webfinger.py b/test/test_webfinger.py index 6e53724..8c605c6 100644 --- a/test/test_webfinger.py +++ b/test/test_webfinger.py @@ -51,17 +51,17 @@ class WebFingerTest(testutil.TestCase): """, url = 'https://foo.com/') - got = app.get_response('/@foo.com', headers={'Accept': 'application/json'}) - mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, - timeout=util.HTTP_TIMEOUT) + got = app.get_response(u'/me@foo.com', headers={'Accept': 'application/json'}) self.assertEquals(200, got.status_int) self.assertEquals('application/json; charset=utf-8', got.headers['Content-Type']) + mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, + timeout=util.HTTP_TIMEOUT) - key = models.MagicKey.get_by_id('@foo.com') + key = models.MagicKey.get_by_id('me@foo.com') self.assertEquals({ - 'subject': 'acct:@foo.com', + 'subject': 'acct:me@foo.com', 'aliases': [ 'https://foo.com/about-me', 'https://foo.com/', @@ -91,7 +91,7 @@ class WebFingerTest(testutil.TestCase): 'href': key.href(), }, { 'rel': 'salmon', - 'href': 'http://localhost/@foo.com/salmon' + 'href': 'http://localhost/me@foo.com/salmon' # TODO # }, { # 'rel': 'self', @@ -105,7 +105,7 @@ class WebFingerTest(testutil.TestCase): # check that magic key is persistent again = json.loads(app.get_response( - '/@foo.com', headers={'Accept': 'application/json'}).body) + '/me@foo.com', headers={'Accept': 'application/json'}).body) self.assertEquals(key.href(), again['magic_keys'][0]['value']) links = {l['rel']: l['href'] for l in again['links']} @@ -120,7 +120,7 @@ class WebFingerTest(testutil.TestCase): """) - got = app.get_response('/@foo.com') + got = app.get_response('/me@foo.com') mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT) self.assertEquals(400, got.status_int) diff --git a/webfinger.py b/webfinger.py index fd37994..86e3b24 100644 --- a/webfinger.py +++ b/webfinger.py @@ -33,8 +33,7 @@ class UserHandler(handlers.XrdOrJrdHandler): def template_prefix(self): return 'templates/webfinger_user' - def template_vars(self, acct): - username, domain = util.parse_acct_uri(acct) + def template_vars(self, username, domain): url = 'http://%s/' % domain # TODO: unify with activitypub @@ -49,14 +48,14 @@ class UserHandler(handlers.XrdOrJrdHandler): Couldn't find a \ representative h-card on %s""" % resp.url) - uri = '%s@%s' % (username, domain) - key = models.MagicKey.get_or_create(uri) + acct = '%s@%s' % (username, domain) + key = models.MagicKey.get_or_create(acct) props = hcard.get('properties', {}) urls = util.dedupe_urls(props.get('url', []) + [resp.url]) canonical_url = urls[0] data = util.trim_nulls({ - 'subject': 'acct:' + uri, + 'subject': 'acct:' + acct, 'aliases': urls, 'magic_keys': [{'value': key.href()}], 'links': sum(([{ @@ -81,7 +80,7 @@ representative h-card on %s""" % resp.url) 'href': key.href(), }, { 'rel': 'salmon', - 'href': '%s/@%s/salmon' % (self.request.host_url, domain), + 'href': '%s/%s/salmon' % (self.request.host_url, acct), }] }) logging.info('Returning WebFinger data: %s', json.dumps(data, indent=2)) @@ -99,6 +98,6 @@ class WebfingerHandler(UserHandler): app = webapp2.WSGIApplication([ - (r'/(?:acct)?(@%s)/?' % common.DOMAIN_RE, UserHandler), + (r'/%s/?' % common.ACCT_RE, UserHandler), ('/.well-known/webfinger', WebfingerHandler), ] + handlers.HOST_META_ROUTES, debug=appengine_config.DEBUG)