handle protocol bot users in webfinger, ids.translate_handle, Web.owns_handle

for #880
pull/968/head
Ryan Barrett 2024-04-22 13:24:24 -07:00
rodzic b9551c4de7
commit 34692abc60
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
9 zmienionych plików z 77 dodań i 12 usunięć

Wyświetl plik

@ -237,7 +237,7 @@ class ATProto(User, Protocol):
return None
def is_blocklisted(url):
def is_blocklisted(url, allow_internal=False):
# don't block common.DOMAINS since we want ourselves, ie our own PDS, to
# be a valid domain to send to
return util.domain_or_parent_in(util.domain_from_link(url), DOMAIN_BLOCKLIST)

12
ids.py
Wyświetl plik

@ -11,7 +11,13 @@ from google.cloud.ndb.query import FilterNode, Query
from granary.bluesky import BSKY_APP_URL_RE, web_url_to_at_uri
from oauth_dropins.webutil import util
from common import subdomain_wrap, LOCAL_DOMAINS, PRIMARY_DOMAIN, SUPERDOMAIN
from common import (
LOCAL_DOMAINS,
PRIMARY_DOMAIN,
PROTOCOL_DOMAINS,
subdomain_wrap,
SUPERDOMAIN,
)
import models
logger = logging.getLogger(__name__)
@ -153,7 +159,9 @@ def translate_handle(*, handle, from_, to, enhanced):
match from_.LABEL, to.LABEL:
case _, 'activitypub':
domain = handle if enhanced else f'{from_.ABBREV}{SUPERDOMAIN}'
domain = f'{from_.ABBREV}{SUPERDOMAIN}'
if enhanced or handle == PRIMARY_DOMAIN or handle in PROTOCOL_DOMAINS:
domain = handle
return f'@{handle}@{domain}'
case _, 'atproto' | 'nostr':

Wyświetl plik

@ -490,13 +490,33 @@ class ActivityPubTest(TestCase):
got = self.client.get('/user.com')
self.assertEqual(404, got.status_code)
# skip _pre_put_hook since it doesn't allow internal domains
@patch.object(Web, '_pre_put_hook', new=lambda self: None)
def test_actor_protocol_bot_user(self, *_):
"""Web users are special cased to drop the /web/ prefix."""
actor_as2 = json_loads(util.read('bsky.brid.gy.as2.json'))
self.make_user('bsky.brid.gy', cls=Web, obj_as2=actor_as2,
obj_id='https://bsky.brid.gy/')
got = self.client.get('/bsky.brid.gy')
self.assertEqual(200, got.status_code)
self.assertEqual(as2.CONTENT_TYPE_LD_PROFILE, got.headers['Content-Type'])
self.assert_equals({
**actor_as2,
'id': 'http://localhost/bsky.brid.gy',
}, got.json, ignore=['inbox', 'outbox', 'endpoints', 'followers',
'following', 'publicKey', 'publicKeyPem'])
# skip _pre_put_hook since it doesn't allow internal domains
@patch.object(Web, '_pre_put_hook', new=lambda self: None)
def test_instance_actor_fetch(self, *_):
def reset_instance_actor():
activitypub._INSTANCE_ACTOR = testutil.global_user
self.addCleanup(reset_instance_actor)
actor_as2 = json_loads(util.read('fed.brid.gy.as2.json'))
self.make_user(common.PRIMARY_DOMAIN, cls=Web, obj_as2=actor_as2)
self.make_user(common.PRIMARY_DOMAIN, cls=Web, obj_as2=actor_as2,
obj_id='https://fed.brid.gy/')
activitypub._INSTANCE_ACTOR = None
got = self.client.get(f'/{common.PRIMARY_DOMAIN}')
@ -2412,5 +2432,3 @@ class ActivityPubUtilsTest(TestCase):
'actor': 'https://fa.brid.gy/ap/fake:user',
'to': [as2.PUBLIC_AUDIENCE],
}, json_loads(kwargs['data']))
# TODO: actor fetch and webfinger for @bsky.brid.gy@bsky.brid.gy both don't work. test and fix those.

Wyświetl plik

@ -110,6 +110,10 @@ class IdsTest(TestCase):
(Web, 'user.com', Fake, 'fake:handle:user.com'),
(Web, 'user.com', Web, 'user.com'),
# instance actor, protocol bot user
(Web, 'fed.brid.gy', ActivityPub, '@fed.brid.gy@fed.brid.gy'),
(Web, 'bsky.brid.gy', ActivityPub, '@bsky.brid.gy@bsky.brid.gy'),
(ActivityPub, '@user@instance', ActivityPub, '@user@instance'),
(ActivityPub, '@user@instance', ATProto, 'user.instance.ap.brid.gy'),
(ActivityPub, '@user@instance', Fake, 'fake:handle:@user@instance'),
@ -137,6 +141,10 @@ class IdsTest(TestCase):
(ActivityPub, '@user@user', Web, 'https://user'),
(ActivityPub, '@user@instance', Fake, 'fake:handle:@user@instance'),
(ATProto, 'user.com', ActivityPub, '@user.com@user.com'),
# instance actor, protocol bot user
(Web, 'fed.brid.gy', ActivityPub, '@fed.brid.gy@fed.brid.gy'),
(Web, 'bsky.brid.gy', ActivityPub, '@bsky.brid.gy@bsky.brid.gy'),
]:
with self.subTest(from_=from_.LABEL, to=to.LABEL):
self.assertEqual(expected, translate_handle(

Wyświetl plik

@ -2373,6 +2373,13 @@ http://this/404s
self.user.ap_subdomain = 'fed'
self.assertEqual('@user.com@fed.brid.gy', self.user.handle_as(ActivityPub))
def test_handle_as_bot_users(self, *_):
fed = Web(id='fed.brid.gy', ap_subdomain='fed')
self.assertEqual('@fed.brid.gy@fed.brid.gy', fed.handle_as(ActivityPub))
bsky = Web(id='bsky.brid.gy', ap_subdomain='bsky')
self.assertEqual('@bsky.brid.gy@bsky.brid.gy', bsky.handle_as(ActivityPub))
def test_id_as(self, *_):
self.assertEqual('http://localhost/user.com', self.user.id_as(ActivityPub))
@ -2552,6 +2559,8 @@ class WebUtilTest(TestCase):
self.assertEqual(False, Web.owns_handle('@foo@bar.com'))
self.assertEqual(False, Web.owns_handle('foo@bar.com'))
self.assertEqual(False, Web.owns_handle('localhost'))
self.assertEqual(True, Web.owns_handle('fed.brid.gy'))
self.assertEqual(True, Web.owns_handle('bsky.brid.gy'))
def test_handle_to_id(self, *_):

Wyświetl plik

@ -4,6 +4,7 @@ from unittest.mock import patch
import urllib.parse
from granary.as2 import CONTENT_TYPE_LD_PROFILE
from oauth_dropins.webutil import util
from oauth_dropins.webutil.testutil import requests_response
# import first so that Fake is defined before URL routes are registered
@ -341,11 +342,29 @@ class WebfingerTest(TestCase):
user = Web.get_by_id('user.com')
assert not user.direct
def test_fed_brid_gy(self):
# skip _pre_put_hook since it doesn't allow internal domains
@patch.object(Web, '_pre_put_hook', new=lambda self: None)
def test_protocol_bot_user(self):
self.make_user('bsky.brid.gy', cls=Web, obj_id='https://bsky.brid.gy/',
ap_subdomain='bsky')
for id in ('acct:bsky.brid.gy@bsky.brid.gy',
'https://bsky.brid.gy/bsky.brid.gy'):
got = self.client.get(f'/.well-known/webfinger?resource={id}')
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
self.assertEqual('acct:bsky.brid.gy@bsky.brid.gy', got.json['subject'])
self.assertEqual(['https://bsky.brid.gy/'], got.json['aliases'])
self.assertIn({
'href': 'http://localhost/bsky.brid.gy',
'rel': 'self',
'type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
}, got.json['links'])
def test_internal_domain_error(self):
got = self.client.get('/.well-known/webfinger?resource=http://localhost/')
self.assertEqual(400, got.status_code, got.get_data(as_text=True))
got = self.client.get('/.well-known/webfinger?resource=acct%3A%40localhost')
got = self.client.get('/.well-known/webfinger?resource=acct:@localhost')
self.assertEqual(400, got.status_code, got.get_data(as_text=True))
@patch('requests.get', return_value=requests_response(

Wyświetl plik

@ -116,7 +116,7 @@ class Fake(User, protocol.Protocol):
return handle.replace(f'{cls.LABEL}:handle:', f'{cls.LABEL}:')
@classmethod
def is_blocklisted(cls, url):
def is_blocklisted(cls, url, allow_internal=False):
return url.startswith(f'{cls.LABEL}:blocklisted')
@classmethod

2
web.py
Wyświetl plik

@ -359,7 +359,7 @@ class Web(User, Protocol):
@classmethod
def owns_handle(cls, handle):
if handle in PROTOCOL_DOMAINS:
if handle == PRIMARY_DOMAIN or handle in PROTOCOL_DOMAINS:
return True
elif not is_valid_domain(handle, allow_internal=False):
return False

Wyświetl plik

@ -14,9 +14,10 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
import activitypub
import common
from common import LOCAL_DOMAINS, SUPERDOMAIN
from common import LOCAL_DOMAINS, PRIMARY_DOMAIN, PROTOCOL_DOMAINS, SUPERDOMAIN
from flask_app import app, cache
from protocol import Protocol
from web import Web
SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe'
@ -58,7 +59,9 @@ class Webfinger(flask_util.XrdOrJrd):
except ValueError:
id = urlparse(resource).netloc or resource
if not cls:
if id == PRIMARY_DOMAIN or id in PROTOCOL_DOMAINS:
cls = Web
elif not cls:
cls = Protocol.for_request(fed='web')
if not cls: