AP users: start to replace external with indirect, starting with webfinger

#512
circle-datastore-transactions
Ryan Barrett 2023-05-29 20:16:15 -07:00
rodzic 624355d85a
commit 93f621aaf5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
7 zmienionych plików z 48 dodań i 24 usunięć

Wyświetl plik

@ -21,7 +21,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
import requests
import common
from common import base64_to_long, long_to_base64
from common import base64_to_long, long_to_base64, redirect_wrap
# maps string label to Protocol subclass. populated by ProtocolUserMeta.
# seed with old and upcoming protocols that don't have their own classes (yet).
@ -187,7 +187,7 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
"""
domain = self.key.id()
if self.actor_as2:
if self.actor_as2 and self.direct:
for url in [u.get('value') if isinstance(u, dict) else u
for u in util.get_list(self.actor_as2, 'url')]:
if url and url.startswith('acct:'):
@ -199,13 +199,21 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
logger.info(f'Defaulting username to domain {domain}')
return domain
# TODO(#512): move to activitypub.py?
def address(self):
"""Returns this user's ActivityPub address, eg '@me@foo.com'."""
return f'@{self.username()}@{self.key.id()}'
if self.direct:
return f'@{self.username()}@{self.key.id()}'
else:
return f'@{self.key.id()}@{request.host}'
# TODO(#512): move to activitypub.py?
def actor_id(self):
"""Returns this user's AS2 actor id, eg 'https://fed.brid.gy/foo.com'."""
return common.host_url(self.key.id())
if self.direct:
return common.host_url(self.key.id())
else:
return redirect_wrap(self.homepage)
def is_homepage(self, url):
"""Returns True if the given URL points to this user's home page."""

Wyświetl plik

@ -76,7 +76,7 @@ def check_web_site():
@app.get(f'/user/<regex("{DOMAIN_RE}"):domain>')
def user(domain):
g.user = Web.get_by_id(domain)
if not g.user:
if not g.user or not g.user.direct:
return USER_NOT_FOUND_HTML, 404
elif g.user.key.id() != domain:
return redirect(f'/user/{g.user.key.id()}', code=301)

Wyświetl plik

@ -87,9 +87,15 @@ class UserTest(TestCase):
g.user.actor_as2 = {'url': ['http://foo', 'acct:bar@foo', 'acct:baz@y.z']}
self.assertEqual('@baz@y.z', g.user.address())
g.user.direct = False
self.assertEqual('@y.z@localhost', g.user.address())
def test_actor_id(self):
self.assertEqual('http://localhost/y.z', g.user.actor_id())
g.user.direct = False
self.assertEqual('http://localhost/r/https://y.z/', g.user.actor_id())
class ObjectTest(TestCase):
def setUp(self):

Wyświetl plik

@ -48,6 +48,12 @@ class PagesTest(TestCase):
got = self.client.get('/user/bar.com')
self.assert_equals(404, got.status_code)
def test_user_not_direct(self):
self.user.direct = False
self.user.put()
got = self.client.get('/user/user.com')
self.assert_equals(404, got.status_code)
def test_user_use_instead(self):
bar = self.make_user('bar.com')
bar.use_instead = self.user.key

Wyświetl plik

@ -9,6 +9,8 @@ from oauth_dropins.webutil.testutil import requests_response
import common
from . import testutil
from web import Web
from .test_web import ACTOR_HTML
WEBFINGER = {
@ -203,7 +205,7 @@ class WebfingerTest(testutil.TestCase):
self.assertEqual(404, got.status_code)
@patch('requests.get')
def test_webfinger_external_user_fetch_creates_user(self, mock_get):
def test_webfinger_external_user_fetch_create_user(self, mock_get):
self.user.key.delete()
mock_get.return_value = requests_response(ACTOR_HTML)
@ -216,6 +218,9 @@ class WebfingerTest(testutil.TestCase):
self.assertEqual(200, got.status_code)
self.assertEqual(expected, got.json)
user = Web.get_by_id('user.com')
assert not user.direct
def test_webfinger_fed_brid_gy(self):
got = self.client.get('/.well-known/webfinger?resource=http://localhost/')
self.assertEqual(400, got.status_code, got.get_data(as_text=True))

Wyświetl plik

@ -131,6 +131,7 @@ class TestCase(unittest.TestCase, testutil.Asserts):
def make_user(domain, cls=Web, **kwargs):
"""Reuse RSA key across Users because generating it is expensive."""
user = cls(id=domain,
direct=True,
mod=global_user.mod,
public_exponent=global_user.public_exponent,
private_exponent=global_user.private_exponent,

Wyświetl plik

@ -33,33 +33,31 @@ class Actor(flask_util.XrdOrJrd):
def template_prefix(self):
return 'webfinger_user'
def template_vars(self, domain=None, external=False):
def template_vars(self, domain=None, allow_indirect=False):
"""
Args:
domain: str, user domain
external: bool, whether this may be an external user, ie without a
stored :class:`User`
allow_indirect: bool, whether this may be an indirect user, ie without
an existing :class:`User`
"""
logger.debug(f'Headers: {list(request.headers.items())}')
if domain.split('.')[-1] in NON_TLDS:
error(f"{domain} doesn't look like a domain", status=404)
g.user = Web.get_by_id(domain)
if g.user:
actor = g.user.to_as1() or {}
homepage = g.user.homepage
handle = g.user.address()
actor_id = g.user.actor_id()
elif external:
g.external_user = homepage = f'https://{domain}/'
obj = Web.load(g.external_user)
actor = obj.as1
handle = f'@{domain}@{request.host}'
actor_id = common.redirect_wrap(homepage)
if allow_indirect:
g.user = Web.get_or_create(domain)
else:
g.user = Web.get_by_id(domain)
if not g.user:
error(f'No user or web site found for {domain}', status=404)
actor = g.user.to_as1() or {}
homepage = g.user.homepage
handle = g.user.address()
actor_id = g.user.actor_id()
logger.info(f'Generating WebFinger data for {domain}')
logger.info(f'AS1 actor: {actor}')
urls = util.dedupe_urls(util.get_list(actor, 'urls') +
@ -141,16 +139,16 @@ class Webfinger(Actor):
if resource in ('', '/', f'acct:{host}', f'acct:@{host}'):
error('Expected other domain, not fed.brid.gy')
external = False
allow_indirect = False
try:
user, domain = util.parse_acct_uri(resource)
if domain in common.DOMAINS:
domain = user
external = True
allow_indirect=True
except ValueError:
domain = urllib.parse.urlparse(resource).netloc or resource
return super().template_vars(domain=domain, external=external)
return super().template_vars(domain=domain, allow_indirect=allow_indirect)
class HostMeta(flask_util.XrdOrJrd):