bug fix for custom username in fediverse address on user page UI

should come from acct: actor URL, not preferredUsername field, which always has to be user's domain for AP interop.

in the process, refactored common.get_username() into User.username().

for #281
pull/321/head
Ryan Barrett 2022-11-26 16:05:02 -08:00
rodzic 6aa59de27f
commit 7d902ec616
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
6 zmienionych plików z 52 dodań i 48 usunięć

Wyświetl plik

@ -1,4 +1,4 @@
<img src="https://raw.github.com/snarfed/bridgy-fed/main/static/bridgy_fed_logo.png" width="120" /> [Bridgy Fed](https://fed.brid.gy/) [![Circle CI](https://circleci.com/gh/snarfed/bridgy-fed.svg?style=svg)](https://circleci.com/gh/snarfed/bridgy-fed) [![Coverage Status](https://coveralls.io/repos/github/snarfed/bridgy-fed/badge.svg?branch=master)](https://coveralls.io/github/snarfed/bridgy-fed?branch=main)
<img src="https://raw.github.com/snarfed/bridgy-fed/main/static/bridgy_fed_logo.png" width="120" /> [Bridgy Fed](https://fed.brid.gy/) [![Circle CI](https://circleci.com/gh/snarfed/bridgy-fed.svg?style=svg)](https://circleci.com/gh/snarfed/bridgy-fed) [![Coverage Status](https://coveralls.io/repos/github/snarfed/bridgy-fed/badge.svg?branch=main)](https://coveralls.io/github/snarfed/bridgy-fed?branch=main)
===
Bridgy Fed connects your web site to [Mastodon](https://joinmastodon.org) and the [fediverse](https://en.wikipedia.org/wiki/Fediverse) via [ActivityPub](https://activitypub.rocks/), [OStatus](https://en.wikipedia.org/wiki/OStatus), [webmentions](https://webmention.net/), and [microformats2](https://microformats.org/wiki/microformats2). Your site gets its own fediverse profile, posts and avatar and header and all. Bridgy Fed translates likes, reposts, mentions, follows, and more back and forth. [See the user docs](https://fed.brid.gy/docs) for more details.

Wyświetl plik

@ -517,6 +517,8 @@ def actor(domain, user=None):
Creates a User for the given domain if one doesn't already exist.
TODO: unify with webfinger.Actor
Args:
domain: str
user: :class:`User`, optional
@ -538,8 +540,6 @@ def actor(domain, user=None):
actor = postprocess_as2(
as2.from_as1(microformats2.json_to_object(hcard)), user=user)
urls = util.dedupe_urls(microformats2.get_string_urls([hcard]))
username = common.get_username(domain, urls)
actor.update({
'id': f'{request.host_url}{domain}',
# This has to be the domain for Mastodon etc interop! It seems like it
@ -559,28 +559,3 @@ def actor(domain, user=None):
logger.info(f'Generated AS2 actor: {json_dumps(actor, indent=2)}')
return actor
def get_username(domain, urls):
"""Returns a user's preferred username from an acct: url, if available.
If there's no acct: URL, returns domain.
Args:
domain: str
urls: sequence of str
Returns: str
"""
assert domain
assert urls
for url in urls:
if url.startswith('acct:'):
urluser, urldomain = util.parse_acct_uri(url)
if urldomain == domain:
logger.info(f'Found custom username: urluser')
return urluser
logger.info(f'Defaulting username to domain {domain}')
return domain

Wyświetl plik

@ -76,15 +76,36 @@ class User(StringIdModel):
magicsigs.base64_to_long(str(self.private_exponent))))
return rsa.exportKey(format='PEM')
def address(self):
"""Returns this user's ActivityPub address, eg '@me@foo.com'."""
def username(self):
"""Returns the user's preferred username from an acct: url, if available.
If there's no acct: URL, or if we haven't found their representative
h-card yet returns their domain.
Args:
domain: str
urls: sequence of str
Returns: str
"""
domain = self.key.id()
username = None
if self.actor_as2 is not None:
username = json_loads(self.actor_as2).get('preferredUsername')
if self.actor_as2:
actor = json_loads(self.actor_as2)
for url in [u.get('value') if isinstance(u, dict) else u
for u in util.get_list(actor, 'urls')]:
if url and url.startswith('acct:'):
urluser, urldomain = util.parse_acct_uri(url)
if urldomain == domain:
logger.info(f'Found custom username: {urluser}')
return urluser
return f'@{username or domain}@{domain}'
logger.info(f'Defaulting username to domain {domain}')
return domain
def address(self):
"""Returns this user's ActivityPub address, eg '@me@foo.com'."""
return f'@{self.username()}@{self.key.id()}'
def user_page_link(self):
"""Returns a pretty user page link with the user's name and profile picture.

Wyświetl plik

@ -46,8 +46,11 @@ class UserTest(testutil.TestCase):
self.user.actor_as2 = '{"type": "Person"}'
self.assertEqual('@y.z@y.z', self.user.address())
self.user.actor_as2 = '{"preferredUsername": "foo"}'
self.assertEqual('@foo@y.z', self.user.address())
self.user.actor_as2 = '{"url": "http://foo"}'
self.assertEqual('@y.z@y.z', self.user.address())
self.user.actor_as2 = '{"urls": ["http://foo", "acct:bar@foo", "acct:baz@y.z"]}'
self.assertEqual('@baz@y.z', self.user.address())
@mock.patch('requests.get')
def test_verify(self, mock_get):

Wyświetl plik

@ -231,12 +231,13 @@ class WebfingerTest(testutil.TestCase):
'acct:foo.com@bridgy-federated.appspot.com',
'acct:foo.com@localhost',
):
url = '/.well-known/webfinger?%s' % urllib.parse.urlencode(
{'resource': resource})
got = self.client.get(url, 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.assertEqual(self.expected_webfinger, got.json)
with self.subTest(resource=resource):
url = '/.well-known/webfinger?%s' % urllib.parse.urlencode(
{'resource': resource})
got = self.client.get(url, 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.assertEqual(self.expected_webfinger, got.json)
def test_webfinger_fed_brid_gy(self):
got = self.client.get('/.well-known/webfinger?resource=http://localhost/')

Wyświetl plik

@ -9,7 +9,7 @@ import re
import urllib.parse
from flask import render_template, request
from granary.microformats2 import get_text
from granary import as2, microformats2
import mf2util
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.flask_util import error
@ -31,7 +31,10 @@ logger = logging.getLogger(__name__)
# make_cache_key=lambda domain: f'{request.path} {request.headers.get("Accept")}')
class Actor(flask_util.XrdOrJrd):
"""Fetches a site's home page, converts its mf2 to WebFinger, and serves."""
"""Fetches a site's home page, converts its mf2 to WebFinger, and serves.
TODO: unify with common.actor()
"""
def template_prefix(self):
return 'webfinger_user'
@ -64,8 +67,9 @@ class Actor(flask_util.XrdOrJrd):
urls = util.dedupe_urls(props.get('url', []) + [resp.url])
canonical_url = urls[0]
username = common.get_username(domain, urls)
acct = f'{username}@{domain}'
user.actor_as2 = json_dumps(common.postprocess_as2(
as2.from_as1(microformats2.json_to_object(hcard)), user=user))
user.put()
# discover atom feed, if any
atom = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
@ -89,7 +93,7 @@ class Actor(flask_util.XrdOrJrd):
# generate webfinger content
data = util.trim_nulls({
'subject': 'acct:' + acct,
'subject': 'acct:' + user.address().lstrip('@'),
'aliases': urls,
'magic_keys': [{'value': user.href()}],
'links': sum(([{
@ -98,7 +102,7 @@ class Actor(flask_util.XrdOrJrd):
'href': url,
}] for url in urls if url.startswith("http")), []) + [{
'rel': 'http://webfinger.net/rel/avatar',
'href': get_text(url),
'href': microformats2.get_text(url),
} for url in props.get('photo', [])] + [{
'rel': 'canonical_uri',
'type': 'text/html',