kopia lustrzana https://github.com/snarfed/bridgy-fed
move domain non-TLD validation from webfinger into Web
fixes https://console.cloud.google.com/errors/detail/CMG7h4PJju67Og;time=P30D?project=bridgy-federateddeploy
rodzic
b8be57bae7
commit
60a4a2bb9f
|
@ -323,7 +323,7 @@ class ActivityPubTest(TestCase):
|
|||
'type': 'Person',
|
||||
'id': 'http://bf/fake/user.com/ap',
|
||||
'preferredUsername': 'user.com',
|
||||
'url': 'http://localhost/r/fake://user.com',
|
||||
'url': 'http://localhost/r/user.com',
|
||||
'summary': '',
|
||||
'inbox': 'http://bf/fake/user.com/ap/inbox',
|
||||
'outbox': 'http://bf/fake/user.com/ap/outbox',
|
||||
|
@ -1456,16 +1456,16 @@ class ActivityPubUtilsTest(TestCase):
|
|||
'actor': {
|
||||
'id': 'baj',
|
||||
'preferredUsername': 'site',
|
||||
'url': 'http://localhost/r/fake://site',
|
||||
'url': 'http://localhost/r/site',
|
||||
},
|
||||
'attributedTo': [{
|
||||
'id': 'bar',
|
||||
'preferredUsername': 'site',
|
||||
'url': 'http://localhost/r/fake://site',
|
||||
'url': 'http://localhost/r/site',
|
||||
}, {
|
||||
'id': 'baz',
|
||||
'preferredUsername': 'site',
|
||||
'url': 'http://localhost/r/fake://site',
|
||||
'url': 'http://localhost/r/site',
|
||||
}],
|
||||
'to': [as2.PUBLIC_AUDIENCE],
|
||||
}, activitypub.postprocess_as2({
|
||||
|
|
|
@ -14,6 +14,7 @@ import common
|
|||
from flask_app import app
|
||||
from models import Object, User
|
||||
import protocol
|
||||
from web import Web
|
||||
|
||||
|
||||
class CommonTest(TestCase):
|
||||
|
@ -47,8 +48,10 @@ class CommonTest(TestCase):
|
|||
|
||||
common.pretty_link('http://foo'))
|
||||
|
||||
# current user's homepage gets converted to BF user page
|
||||
g.user = Web(id='user.com')
|
||||
self.assertEqual(
|
||||
'<a class="h-card u-author" href="/fa/user.com"><img src="" class="profile"> user.com</a>',
|
||||
'<a class="h-card u-author" href="/web/user.com"><img src="" class="profile"> user.com</a>',
|
||||
common.pretty_link('https://user.com/'))
|
||||
|
||||
def test_redirect_wrap_empty(self):
|
||||
|
|
|
@ -219,10 +219,10 @@ class FollowerTest(TestCase):
|
|||
|
||||
def test_from_to_same_type_fails(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
Follower(from_=Web.key_for('foo'), to=Web.key_for('bar')).put()
|
||||
Follower(from_=Web.key_for('foo.com'), to=Web.key_for('bar.com')).put()
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
Follower.get_or_create(from_=Web(id='foo'), to=Web(id='bar'))
|
||||
Follower.get_or_create(from_=Web(id='foo.com'), to=Web(id='bar.com'))
|
||||
|
||||
def test_get_or_create(self):
|
||||
follower = Follower.get_or_create(from_=g.user, to=self.other_user)
|
||||
|
|
|
@ -115,7 +115,7 @@ class ProtocolTest(TestCase):
|
|||
(None, None),
|
||||
('', None),
|
||||
('foo://bar', None),
|
||||
('fake://foo', Fake),
|
||||
('fake:foo', Fake),
|
||||
# TODO
|
||||
# ('at://foo', ATProto),
|
||||
('https://ap.brid.gy/foo/bar', ActivityPub),
|
||||
|
|
|
@ -1707,8 +1707,8 @@ class WebProtocolTest(TestCase):
|
|||
for id in 'user.com', 'http://user.com', 'https://user.com/':
|
||||
self.assertEqual(Web(id='user.com').key, Web.key_for(id))
|
||||
|
||||
for bad in None, '', 'foo', 'https://foo/', 'foo bar':
|
||||
with self.assertRaises(AssertionError):
|
||||
for bad in None, '', 'foo', 'https://foo/', 'foo bar', 'user.json':
|
||||
with self.subTest(bad=bad), self.assertRaises(ValueError):
|
||||
Web.key_for(bad)
|
||||
|
||||
def test_owns_id(self, *_):
|
||||
|
|
|
@ -82,32 +82,32 @@ WEBFINGER_NO_HCARD = {
|
|||
}],
|
||||
}
|
||||
WEBFINGER_FAKE = {
|
||||
'subject': 'acct:user@fake',
|
||||
'aliases': ['fake://user/'],
|
||||
'subject': 'acct:fake:user@fake',
|
||||
'aliases': ['fake:user'],
|
||||
'links': [{
|
||||
'rel': 'canonical_uri',
|
||||
'type': 'text/html',
|
||||
'href': 'fake://user/',
|
||||
'href': 'fake:user',
|
||||
}, {
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': 'http://bf/fake/user/ap',
|
||||
'href': 'http://bf/fake/fake:user/ap',
|
||||
}, {
|
||||
'rel': 'inbox',
|
||||
'type': 'application/activity+json',
|
||||
'href': 'http://bf/fake/user/ap/inbox',
|
||||
'href': 'http://bf/fake/fake:user/ap/inbox',
|
||||
}, {
|
||||
'rel': 'sharedInbox',
|
||||
'type': 'application/activity+json',
|
||||
'href': 'http://localhost/ap/sharedInbox',
|
||||
}, {
|
||||
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'template': 'http://localhost/fa/user?url={uri}',
|
||||
'template': 'http://localhost/fa/fake:user?url={uri}',
|
||||
}],
|
||||
}
|
||||
WEBFINGER_FAKE_FED_BRID_GY = copy.deepcopy(WEBFINGER_FAKE)
|
||||
WEBFINGER_FAKE_FED_BRID_GY['links'][3]['href'] = 'https://fed.brid.gy/ap/sharedInbox'
|
||||
WEBFINGER_FAKE_FED_BRID_GY['links'][4]['template'] = 'https://fed.brid.gy/fa/user?url={uri}'
|
||||
WEBFINGER_FAKE_FED_BRID_GY['links'][4]['template'] = 'https://fed.brid.gy/fa/fake:user?url={uri}'
|
||||
|
||||
|
||||
class HostMetaTest(TestCase):
|
||||
|
@ -162,33 +162,37 @@ class WebfingerTest(TestCase):
|
|||
self.assert_equals(WEBFINGER, got.json)
|
||||
|
||||
def test_user_infer_protocol_from_resource_subdomain(self):
|
||||
got = self.client.get('/.well-known/webfinger?resource=acct:user@fake.brid.gy',
|
||||
headers={'Accept': 'application/json'})
|
||||
got = self.client.get(
|
||||
'/.well-known/webfinger?resource=acct:fake:user@fake.brid.gy',
|
||||
headers={'Accept': 'application/json'})
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual('application/jrd+json', got.headers['Content-Type'])
|
||||
self.assert_equals(WEBFINGER_FAKE, got.json)
|
||||
|
||||
def test_user_infer_protocol_from_request_subdomain(self):
|
||||
self.make_user('user', cls=Fake)
|
||||
got = self.client.get('/.well-known/webfinger?resource=acct:user@user',
|
||||
base_url='https://fake.brid.gy/',
|
||||
headers={'Accept': 'application/json'})
|
||||
self.make_user('fake:user', cls=Fake)
|
||||
got = self.client.get(
|
||||
'/.well-known/webfinger?resource=acct:user@fake:user',
|
||||
base_url='https://fake.brid.gy/',
|
||||
headers={'Accept': 'application/json'})
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual('application/jrd+json', got.headers['Content-Type'])
|
||||
self.assert_equals(WEBFINGER_FAKE_FED_BRID_GY, got.json)
|
||||
|
||||
def test_user_infer_protocol_resource_overrides_request(self):
|
||||
got = self.client.get('/.well-known/webfinger?resource=acct:user@fake.brid.gy',
|
||||
base_url='https://ap.brid.gy/',
|
||||
headers={'Accept': 'application/json'})
|
||||
got = self.client.get(
|
||||
'/.well-known/webfinger?resource=acct:fake:user@fake.brid.gy',
|
||||
base_url='https://ap.brid.gy/',
|
||||
headers={'Accept': 'application/json'})
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual('application/jrd+json', got.headers['Content-Type'])
|
||||
self.assert_equals(WEBFINGER_FAKE_FED_BRID_GY, got.json)
|
||||
|
||||
def test_urlencoded(self):
|
||||
"""https://github.com/snarfed/bridgy-fed/issues/535"""
|
||||
got = self.client.get('/.well-known/webfinger?resource=acct%3Auser.com%40user.com',
|
||||
headers={'Accept': 'application/json'})
|
||||
got = self.client.get(
|
||||
'/.well-known/webfinger?resource=acct%3Auser.com%40user.com',
|
||||
headers={'Accept': 'application/json'})
|
||||
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
|
||||
self.assertEqual('application/jrd+json', got.headers['Content-Type'])
|
||||
self.assert_equals(WEBFINGER, got.json)
|
||||
|
@ -216,7 +220,6 @@ class WebfingerTest(TestCase):
|
|||
# Mastodon requires this as of 3.3.0
|
||||
# https://github.com/snarfed/bridgy-fed/issues/73
|
||||
'acct:user.com@fed.brid.gy',
|
||||
'acct:user.com@bridgy-federated.appspot.com',
|
||||
'acct:user.com@localhost',
|
||||
):
|
||||
with self.subTest(resource=resource):
|
||||
|
@ -237,14 +240,31 @@ class WebfingerTest(TestCase):
|
|||
}, got.json)
|
||||
|
||||
def test_missing_user(self):
|
||||
for bad in 'nope.com', 'nope.com@nope.com':
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:{bad}')
|
||||
self.assertEqual(404, got.status_code)
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:nope.com@nope.com')
|
||||
self.assertEqual(404, got.status_code)
|
||||
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:nope.com')
|
||||
self.assertEqual(400, got.status_code)
|
||||
|
||||
def test_indirect_user_not_on_bridgy_fed_subdomain(self):
|
||||
self.user.direct = False
|
||||
self.user.put()
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:user.com@user.com')
|
||||
self.assertEqual(404, got.status_code)
|
||||
|
||||
def test_bad_id(self):
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:nope@fa.brid.gy')
|
||||
self.assertEqual(400, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:nope@nope',
|
||||
base_url='https://fa.brid.gy/')
|
||||
self.assertEqual(400, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
def test_bad_tld(self):
|
||||
self.make_user('user.json')
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:user.json@user.json')
|
||||
self.assertIn("like a domain but", html.unescape(got.get_data(as_text=True)))
|
||||
got = self.client.get(f'/.well-known/webfinger?resource=acct:user.json@user.json',
|
||||
base_url='https://web.brid.gy/')
|
||||
self.assertEqual(400, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
@patch('requests.get')
|
||||
def test_fetch_create_user(self, mock_get):
|
||||
|
|
|
@ -48,7 +48,7 @@ class Fake(User, protocol.Protocol):
|
|||
fetched = []
|
||||
|
||||
def web_url(self):
|
||||
return f'fake://{self.key.id()}'
|
||||
return self.key.id()
|
||||
|
||||
def ap_address(self):
|
||||
return f'@{self.key.id()}@fake'
|
||||
|
@ -58,7 +58,10 @@ class Fake(User, protocol.Protocol):
|
|||
|
||||
@classmethod
|
||||
def owns_id(cls, id):
|
||||
return id.startswith('fake://')
|
||||
if id.startswith('nope'):
|
||||
return False
|
||||
|
||||
return id.startswith('fake:') or id in cls.objects
|
||||
|
||||
@classmethod
|
||||
def send(cls, obj, url, log_data=True):
|
||||
|
@ -89,7 +92,7 @@ class Fake(User, protocol.Protocol):
|
|||
# expensive to generate
|
||||
requests.post(f'http://{ndb_client.host}/reset')
|
||||
with ndb_client.context():
|
||||
global_user = Fake.get_or_create('user')
|
||||
global_user = Fake.get_or_create('fake:user')
|
||||
|
||||
|
||||
# import other modules that register Flask handlers *after* Fake is defined
|
||||
|
|
18
web.py
18
web.py
|
@ -39,6 +39,7 @@ CHAR_AFTER_SPACE = chr(ord(' ') + 1)
|
|||
WWW_DOMAINS = frozenset((
|
||||
'www.jvt.me',
|
||||
))
|
||||
NON_TLDS = frozenset(('html', 'json', 'php', 'xml'))
|
||||
|
||||
|
||||
class Web(User, Protocol):
|
||||
|
@ -211,8 +212,12 @@ class Web(User, Protocol):
|
|||
|
||||
Args:
|
||||
id: str
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
"""
|
||||
assert id
|
||||
if not id:
|
||||
raise ValueError()
|
||||
|
||||
if util.is_web(id):
|
||||
parsed = urlparse(id)
|
||||
|
@ -220,13 +225,16 @@ class Web(User, Protocol):
|
|||
id = parsed.netloc
|
||||
|
||||
if re.match(common.DOMAIN_RE, id):
|
||||
tld = id.split('.')[-1]
|
||||
if tld in NON_TLDS:
|
||||
raise ValueError(f"{id} looks like a domain but {tld} isn't a TLD")
|
||||
return cls(id=id).key
|
||||
|
||||
assert False, f'{id} is not a domain or usable home page URL'
|
||||
raise ValueError(f'{id} is not a domain or usable home page URL')
|
||||
|
||||
@classmethod
|
||||
def owns_id(cls, id):
|
||||
"""Returns None if id is an http(s) URL, False otherwise.
|
||||
"""Returns None if id is a domain or http(s) URL, False otherwise.
|
||||
|
||||
All web pages are http(s) URLs, but not all http(s) URLs are web pages.
|
||||
"""
|
||||
|
@ -238,8 +246,8 @@ class Web(User, Protocol):
|
|||
if key:
|
||||
user = key.get()
|
||||
return True if user and user.has_redirects else None
|
||||
except AssertionError:
|
||||
pass
|
||||
except ValueError as e:
|
||||
logger.info(e)
|
||||
|
||||
return None if util.is_web(id) else False
|
||||
|
||||
|
|
16
webfinger.py
16
webfinger.py
|
@ -20,8 +20,6 @@ from models import User
|
|||
from protocol import Protocol
|
||||
from web import Web
|
||||
|
||||
NON_TLDS = frozenset(('html', 'json', 'php', 'xml'))
|
||||
|
||||
SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -55,8 +53,8 @@ class Webfinger(flask_util.XrdOrJrd):
|
|||
cls = None
|
||||
try:
|
||||
user, id = util.parse_acct_uri(resource)
|
||||
if id in common.DOMAINS:
|
||||
cls = Protocol.for_domain(id)
|
||||
cls = Protocol.for_domain(id, fed=Web)
|
||||
if cls:
|
||||
id = user
|
||||
allow_indirect=True
|
||||
except ValueError:
|
||||
|
@ -66,12 +64,8 @@ class Webfinger(flask_util.XrdOrJrd):
|
|||
cls = Protocol.for_request(fed=Web)
|
||||
|
||||
logger.info(f'Protocol {cls.__name__}, user id {id}')
|
||||
|
||||
# if id is a domain, validate
|
||||
if re.match(common.DOMAIN_RE, id):
|
||||
tld = id.split('.')[-1]
|
||||
if tld in NON_TLDS:
|
||||
error(f"{id} looks like a domain but {tld} isn't a TLD", status=404)
|
||||
if cls.owns_id(id) is False:
|
||||
error(f'{id} is not a valid {cls.__name__} id')
|
||||
|
||||
# only allow indirect users if this id is "on" a brid.gy subdomain,
|
||||
# eg user.com@bsky.brid.gy but not user.com@user.com
|
||||
|
@ -79,6 +73,8 @@ class Webfinger(flask_util.XrdOrJrd):
|
|||
g.user = cls.get_or_create(id)
|
||||
else:
|
||||
g.user = cls.get_by_id(id)
|
||||
if g.user and not g.user.direct:
|
||||
error(f"{g.user.key} hasn't signed up yet", status=404)
|
||||
|
||||
if not g.user:
|
||||
error(f'No {cls.LABEL} user found for {id}', status=404)
|
||||
|
|
Ładowanie…
Reference in New Issue