standardize AP endpoints mostly onto protocol subdomains

fixes #611
pull/653/head
Ryan Barrett 2023-09-26 20:04:49 -07:00
rodzic 4d975f43fd
commit 8cd1bf809b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
2 zmienionych plików z 54 dodań i 47 usunięć

Wyświetl plik

@ -706,21 +706,16 @@ def postprocess_as2_actor(actor, wrap=True):
# source protocol in subdomain. # source protocol in subdomain.
# WARNING: the user page handler in pages.py overrides this for fediverse # WARNING: the user page handler in pages.py overrides this for fediverse
# addresses with leading @ character. be careful when changing this route! # addresses with leading @ character. be careful when changing this route!
@app.get(f'/ap/<handle_or_id>', defaults={'protocol': None}) @app.get(f'/ap/<handle_or_id>')
# source protocol in path; primarily for localhost testing # source protocol in path; primarily for backcompat
@app.get(f'/ap/<any({",".join(PROTOCOLS)}):protocol>/<handle_or_id>') @app.get(f'/ap/web/<handle_or_id>')
# special case Web users without /ap/web/ prefix, for backward compatibility # special case Web users without /ap/web/ prefix, for backward compatibility
@app.get(f'/<regex("{DOMAIN_RE}"):handle_or_id>', defaults={'protocol': 'web'}) @app.get(f'/<regex("{DOMAIN_RE}"):handle_or_id>')
@flask_util.cached(cache, CACHE_TIME) @flask_util.cached(cache, CACHE_TIME)
def actor(protocol, handle_or_id): def actor(handle_or_id):
"""Serves a user's AS2 actor from the datastore.""" """Serves a user's AS2 actor from the datastore."""
if protocol: cls = Protocol.for_request(fed=PROTOCOLS['web'])
cls = PROTOCOLS[protocol] assert cls
else:
cls = Protocol.for_request(fed=None)
if not cls:
error(f"Couldn't determine protocol")
if cls.owns_id(handle_or_id) is False: if cls.owns_id(handle_or_id) is False:
if cls.owns_handle(handle_or_id) is False: if cls.owns_handle(handle_or_id) is False:
@ -778,11 +773,12 @@ def actor(protocol, handle_or_id):
# note that this path overlaps with the /ap/<handle_or_id> actor route above, # note that this path overlaps with the /ap/<handle_or_id> actor route above,
# but doesn't collide because this is POST and that one is GET. # but doesn't collide because this is POST and that one is GET.
@app.post('/ap/sharedInbox') @app.post('/ap/sharedInbox')
@app.post(f'/ap/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>/inbox') # TODO: protocol in subdomain
@app.post(f'/ap/<any({",".join(PROTOCOLS)}):protocol>/<id>/inbox')
# special case Web users without /ap/web/ prefix, for backward compatibility # special case Web users without /ap/web/ prefix, for backward compatibility
@app.post('/inbox') @app.post('/inbox')
@app.post(f'/<regex("{DOMAIN_RE}"):domain>/inbox', defaults={'protocol': 'web'}) @app.post(f'/<regex("{DOMAIN_RE}"):id>/inbox')
def inbox(protocol=None, domain=None): def inbox(protocol='web', id=None):
"""Handles ActivityPub inbox delivery.""" """Handles ActivityPub inbox delivery."""
# parse and validate AS2 activity # parse and validate AS2 activity
try: try:
@ -799,23 +795,23 @@ def inbox(protocol=None, domain=None):
# load receiving user # load receiving user
obj_id = as1.get_object(redirect_unwrap(activity)).get('id') obj_id = as1.get_object(redirect_unwrap(activity)).get('id')
receiving_proto = receiving_user_id = None to_proto = None
if protocol: if protocol:
receiving_proto = PROTOCOLS[protocol] to_proto = PROTOCOLS[protocol]
elif type == 'Follow': elif type == 'Follow':
receiving_proto = Protocol.for_id(obj_id) to_proto = Protocol.for_id(obj_id)
if receiving_proto: if to_proto:
if domain: to_user_id = None
assert receiving_proto is web.Web, 'https://github.com/snarfed/bridgy-fed/issues/611' if id:
receiving_user_id = domain to_user_id = id
else: else:
receiving_key = receiving_proto.key_for(obj_id) to_key = to_proto.key_for(obj_id)
if receiving_key: if to_key:
receiving_user_id = receiving_key.id() to_user_id = to_key.id()
if receiving_user_id: if to_user_id:
g.user = receiving_proto.get_or_create(receiving_user_id, direct=False) g.user = to_proto.get_or_create(to_user_id, direct=False)
logger.info(f'Setting g.user to {g.user.key}') logger.info(f'Setting g.user to {g.user.key}')
if not g.user.direct and actor_id: if not g.user.direct and actor_id:
# this is a deliberate interaction with an indirect receiving user; # this is a deliberate interaction with an indirect receiving user;
@ -849,21 +845,25 @@ def inbox(protocol=None, domain=None):
return ActivityPub.receive(obj) return ActivityPub.receive(obj)
@app.get(f'/ap/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>/<any(followers,following):collection>') # protocol in subdomain
@app.get(f'/ap/<id>/<any(followers,following):collection>')
# source protocol in path; primarily for backcompat
@app.get(f'/ap/web/<regex("{DOMAIN_RE}"):id>/<any(followers,following):collection>')
# special case Web users without /ap/web/ prefix, for backward compatibility # special case Web users without /ap/web/ prefix, for backward compatibility
@app.get(f'/<regex("{DOMAIN_RE}"):domain>/<any(followers,following):collection>', @app.get(f'/<regex("{DOMAIN_RE}"):id>/<any(followers,following):collection>')
defaults={'protocol': 'web'})
@flask_util.cached(cache, CACHE_TIME) @flask_util.cached(cache, CACHE_TIME)
def follower_collection(protocol, domain, collection): def follower_collection(id, collection):
"""ActivityPub Followers and Following collections. """ActivityPub Followers and Following collections.
https://www.w3.org/TR/activitypub/#followers * https://www.w3.org/TR/activitypub/#followers
https://www.w3.org/TR/activitypub/#collections * https://www.w3.org/TR/activitypub/#collections
https://www.w3.org/TR/activitystreams-core/#paging * https://www.w3.org/TR/activitystreams-core/#paging
""" """
g.user = PROTOCOLS[protocol].get_by_id(domain) protocol = Protocol.for_request(fed=PROTOCOLS['web'])
assert protocol
g.user = protocol.get_by_id(id)
if not g.user: if not g.user:
return f'{protocol} user {domain} not found', 404 return f'{protocol} user {id} not found', 404
# page # page
followers, new_before, new_after = Follower.fetch_page(collection) followers, new_before, new_after = Follower.fetch_page(collection)
@ -896,7 +896,7 @@ def follower_collection(protocol, domain, collection):
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': request.base_url, 'id': request.base_url,
'type': 'Collection', 'type': 'Collection',
'summary': f"{domain}'s {collection}", 'summary': f"{id}'s {collection}",
'totalItems': count, 'totalItems': count,
'first': page, 'first': page,
} }
@ -904,14 +904,19 @@ def follower_collection(protocol, domain, collection):
return collection, {'Content-Type': as2.CONTENT_TYPE} return collection, {'Content-Type': as2.CONTENT_TYPE}
@app.get(f'/ap/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>/outbox') # protocol in subdomain
@app.get(f'/ap/<id>/outbox')
# source protocol in path; primarily for backcompat
@app.get(f'/ap/web/<regex("{DOMAIN_RE}"):id>/outbox')
# special case Web users without /ap/web/ prefix, for backward compatibility # special case Web users without /ap/web/ prefix, for backward compatibility
@app.get(f'/<regex("{DOMAIN_RE}"):domain>/outbox', defaults={'protocol': 'web'}) @app.get(f'/<regex("{DOMAIN_RE}"):id>/outbox')
def outbox(protocol, domain): def outbox(id):
protocol = Protocol.for_request(fed=PROTOCOLS['web'])
assert protocol
return { return {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': request.url, 'id': request.url,
'summary': f"{domain}'s outbox", 'summary': f"{id}'s outbox",
'type': 'OrderedCollection', 'type': 'OrderedCollection',
'totalItems': 0, 'totalItems': 0,
'first': { 'first': {

Wyświetl plik

@ -1340,17 +1340,18 @@ class ActivityPubTest(TestCase):
def test_followers_collection_fake(self, *_): def test_followers_collection_fake(self, *_):
self.make_user('foo.com', cls=Fake) self.make_user('foo.com', cls=Fake)
resp = self.client.get('/ap/fake/foo.com/followers') resp = self.client.get('/ap/foo.com/followers',
base_url='https://fa.brid.gy')
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.assertEqual({ self.assertEqual({
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://localhost/ap/fake/foo.com/followers', 'id': 'https://fa.brid.gy/ap/foo.com/followers',
'type': 'Collection', 'type': 'Collection',
'summary': "foo.com's followers", 'summary': "foo.com's followers",
'totalItems': 0, 'totalItems': 0,
'first': { 'first': {
'type': 'CollectionPage', 'type': 'CollectionPage',
'partOf': 'http://localhost/ap/fake/foo.com/followers', 'partOf': 'https://fa.brid.gy/ap/foo.com/followers',
'items': [], 'items': [],
}, },
}, resp.json) }, resp.json)
@ -1471,17 +1472,18 @@ class ActivityPubTest(TestCase):
def test_outbox_fake(self, *_): def test_outbox_fake(self, *_):
self.make_user('foo.com', cls=Fake) self.make_user('foo.com', cls=Fake)
resp = self.client.get(f'/ap/fake/foo.com/outbox') resp = self.client.get(f'/ap/foo.com/outbox',
base_url='https://fa.brid.gy')
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.assertEqual({ self.assertEqual({
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://localhost/ap/fake/foo.com/outbox', 'id': 'https://fa.brid.gy/ap/foo.com/outbox',
'summary': "foo.com's outbox", 'summary': "foo.com's outbox",
'type': 'OrderedCollection', 'type': 'OrderedCollection',
'totalItems': 0, 'totalItems': 0,
'first': { 'first': {
'type': 'CollectionPage', 'type': 'CollectionPage',
'partOf': 'http://localhost/ap/fake/foo.com/outbox', 'partOf': 'https://fa.brid.gy/ap/foo.com/outbox',
'items': [], 'items': [],
}, },
}, resp.json) }, resp.json)