kopia lustrzana https://github.com/snarfed/bridgy-fed
mastodon interop: salmon + activitypub + webfinger cleanup, tests
rodzic
b975d68a72
commit
4dd8f7e1a7
|
@ -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)
|
||||
|
|
2
app.yaml
2
app.yaml
|
@ -74,7 +74,7 @@ handlers:
|
|||
script: salmon.app
|
||||
secure: always
|
||||
|
||||
- url: /(acct:)?@[^/]+/?
|
||||
- url: /(acct:)?([^@]+)@@[^/]+/?
|
||||
script: webfinger.app
|
||||
secure: always
|
||||
|
||||
|
|
|
@ -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/)',
|
||||
}
|
||||
|
|
14
salmon.py
14
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)
|
||||
|
|
|
@ -64,8 +64,8 @@ class SalmonTest(testutil.TestCase):
|
|||
<title>My Reply</title>
|
||||
<updated>%s</updated>
|
||||
</entry>""" % 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
|
||||
|
|
|
@ -51,17 +51,17 @@ class WebFingerTest(testutil.TestCase):
|
|||
</body>
|
||||
""", 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):
|
|||
</div>
|
||||
</body>
|
||||
""")
|
||||
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)
|
||||
|
|
13
webfinger.py
13
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 <a href="http://microformats.org/wiki/representative-hcard-parsing">\
|
||||
representative h-card</a> 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</a> 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)
|
||||
|
|
Ładowanie…
Reference in New Issue