kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
754d694823
commit
65f3ef3cc7
30
common.py
30
common.py
|
@ -16,6 +16,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
|
|||
import requests
|
||||
from werkzeug.exceptions import BadGateway
|
||||
|
||||
import common
|
||||
from models import Activity, User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -476,9 +477,11 @@ 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}',
|
||||
'preferredUsername': domain,
|
||||
'preferredUsername': username,
|
||||
'inbox': f'{request.host_url}{domain}/inbox',
|
||||
'outbox': f'{request.host_url}{domain}/outbox',
|
||||
'following': f'{request.host_url}{domain}/following',
|
||||
|
@ -490,3 +493,28 @@ 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
|
||||
|
|
15
models.py
15
models.py
|
@ -10,6 +10,7 @@ from django_salmon import magicsigs
|
|||
from flask import request
|
||||
from google.cloud import ndb
|
||||
from oauth_dropins.webutil.models import StringIdModel
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
import common
|
||||
|
||||
|
@ -34,6 +35,7 @@ class User(StringIdModel):
|
|||
private_exponent = ndb.StringProperty(required=True)
|
||||
has_redirects = ndb.BooleanProperty()
|
||||
has_hcard = ndb.BooleanProperty()
|
||||
actor_as2 = ndb.TextProperty()
|
||||
|
||||
@classmethod
|
||||
def _get_kind(cls):
|
||||
|
@ -72,6 +74,16 @@ 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'."""
|
||||
domain = self.key.id()
|
||||
|
||||
username = None
|
||||
if self.actor_as2 is not None:
|
||||
username = json_loads(self.actor_as2).get('preferredUsername')
|
||||
|
||||
return f'@{username or domain}@{domain}'
|
||||
|
||||
def verify(self):
|
||||
"""Fetches site a couple ways to check for redirects and h-card."""
|
||||
domain = self.key.id()
|
||||
|
@ -91,9 +103,10 @@ class User(StringIdModel):
|
|||
|
||||
# check home page
|
||||
try:
|
||||
common.actor(self.key.id(), user=self)
|
||||
self.actor_as2 = json_dumps(common.actor(self.key.id(), user=self))
|
||||
self.has_hcard = True
|
||||
except (BadRequest, NotFound):
|
||||
self.actor_as2 = None
|
||||
self.has_hcard = False
|
||||
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ button[disabled]:hover {
|
|||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.btn-home img {
|
||||
.btn-home img, .logo {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,11 +69,7 @@ We're aware of the sites below, and we've made progress on some, but they're not
|
|||
<li class="answer">
|
||||
|
||||
<p>
|
||||
Federated social network identities take the form <code>@username@example.com</code>, like an email address with a leading <code>@</code>. Your site's identity via Bridgy Fed will be <code>@yourdomain.com@yourdomain.com</code>. Once you've <a href="#setup">set up Atom on your site</a>, people can follow you at that address.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Most fedsocnets also publish Atom themselves, so you can add profile URLs like <a href="https://mastodon.technology/@snarfed">mastodon.technology/@snarfed</a> to your <a href="https://indieweb.org/reader">reader</a> and see their posts there too.
|
||||
Federated social network identities take the form <code>@username@example.com</code>, like an email address with a leading <code>@</code>. Your site's identity via Bridgy Fed will be <code>@yourdomain.com@yourdomain.com</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -159,10 +155,6 @@ https://en.support.wordpress.com/site-redirect/
|
|||
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
If you want people on OStatus sites like Hubzilla to see your posts, your web site will also need to support <a href="https://indieweb.org/WebSub">WebSub</a> (née PubSubHubbub). Specifically, <a href="https://blog.superfeedr.com/howto-pubsubhubbub/#discovery">your Atom feed needs to advertise it</a>. <a href="https://github.com/tootsuite/mastodon/issues/1441#issuecomment-302969948">Example details for Mastodon.</a> If you're on a CMS, it may already have a plugin! <a href="https://wordpress.org/">WordPress</a> has <a href="https://indieweb.org/WebSub#WordPress_Plugins_for_PuSH">a couple</a>, and <a href="http://withknown.com/">Known</a> has it <a href="https://indieweb.org/WebSub#1000s_of_Known_Sites">built in</a>. Or you can use <a href="https://superfeedr.com/publisher">Superfeedr</a> or <a href="https://switchboard.p3k.io/">Switchboard</a>.
|
||||
</p>
|
||||
|
||||
</ol>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="col-lg-6 col-md-12">
|
||||
<a class="btn btn-default btn-home" href="https://brid.gy/mastodon/start">
|
||||
<p class="bigger">Cross-post to a Mastodon account:<br>
|
||||
@user@mastodon.server</p>
|
||||
@you@mastodon.server</p>
|
||||
<pre class="bigger"> <img title="Pleroma" style="height: 1.5em; margin-bottom: -.2em" src="/static/pleroma_logo.svg">
|
||||
/
|
||||
<span title="Your web site">🌐</span> — <span class="highlight"><img title="Mastodon" src="/oauth_dropins_static/mastodon_2x.png"></span> — <img title="Friendica" src="/static/friendica_logo.svg">
|
||||
|
@ -25,7 +25,7 @@
|
|||
<div class="col-lg-6 col-md-12">
|
||||
<a class="btn btn-default btn-home" href="/web-site">
|
||||
<p class="bigger">Connect directly to the fediverse:<br>
|
||||
@website.com</p>
|
||||
@yoursite.com</p>
|
||||
<pre class="bigger"><img title="Fediverse" src="/static/fediverse_logo.svg"> <img title="PeerTube" src="/static/peertube_logo.svg">
|
||||
\ /
|
||||
<img title="Misskey" src="/static/misskey_logo.png"> — <span class="highlight" title="Your web site">🌐</span> — <img title="Mastodon" src="/oauth_dropins_static/mastodon_2x.png">
|
||||
|
|
|
@ -21,14 +21,16 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<h2 style="display: inline">
|
||||
<a href="https://{{ domain }}/">🌐 {{ domain }}</a>
|
||||
<span title="Fediverse address">
|
||||
<img class="logo" src="/static/fediverse_logo.svg"> {{ user.address() }}</span> |
|
||||
<a title="Web site" href="https://{{ domain }}/">🌐 {{ domain }}</a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 style="display: inline">
|
||||
| <a href="/user/{{ domain }}/followers">{{ followers }} follower{% if followers != '1' %}s{% endif %}</a>
|
||||
<a href="/user/{{ domain }}/followers">{{ followers }} follower{% if followers != '1' %}s{% endif %}</a>
|
||||
| <a href="/user/{{ domain }}/following">following {{ following }}</a>
|
||||
| <a href="/user/{{ domain }}/feed">HTML</a>,
|
||||
<a href="/user/{{ domain }}/feed?format=atom">Atom</a>,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from unittest import mock
|
||||
|
||||
from oauth_dropins.webutil.testutil import requests_response
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
from app import app
|
||||
from models import User, Activity
|
||||
|
@ -15,7 +16,7 @@ class UserTest(testutil.TestCase):
|
|||
super(UserTest, self).setUp()
|
||||
self.user = User.get_or_create('y.z')
|
||||
|
||||
def test_magic_key_get_or_create(self):
|
||||
def test_get_or_create(self):
|
||||
assert self.user.mod
|
||||
assert self.user.public_exponent
|
||||
assert self.user.private_exponent
|
||||
|
@ -39,52 +40,75 @@ class UserTest(testutil.TestCase):
|
|||
self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem)
|
||||
self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem)
|
||||
|
||||
def test_address(self):
|
||||
self.assertEqual('@y.z@y.z', self.user.address())
|
||||
|
||||
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())
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_verify(self, mock_get):
|
||||
self.assertFalse(self.user.has_redirects)
|
||||
self.assertFalse(self.user.has_hcard)
|
||||
|
||||
def check(redirects, hcard):
|
||||
def check(redirects, hcard, actor):
|
||||
with app.test_request_context('/'):
|
||||
self.user.verify()
|
||||
with self.subTest(redirects=redirects, hcard=hcard):
|
||||
self.assertEqual(redirects, bool(self.user.has_redirects))
|
||||
self.assertEqual(hcard, bool(self.user.has_hcard))
|
||||
with self.subTest(redirects=redirects, hcard=hcard, actor=actor):
|
||||
self.assert_equals(redirects, bool(self.user.has_redirects))
|
||||
self.assert_equals(hcard, bool(self.user.has_hcard))
|
||||
if actor is None:
|
||||
self.assertIsNone(self.user.actor_as2)
|
||||
else:
|
||||
got = {k: v for k, v in json_loads(self.user.actor_as2).items()
|
||||
if k in actor}
|
||||
self.assert_equals(actor, got)
|
||||
|
||||
# both fail
|
||||
empty = requests_response('')
|
||||
mock_get.side_effect = [empty, empty]
|
||||
check(False, False)
|
||||
check(False, False, None)
|
||||
|
||||
# redirect works but strips query params, no h-card
|
||||
half_redir = requests_response(
|
||||
status=302, redirected_url='http://localhost/.well-known/webfinger')
|
||||
no_hcard = requests_response('<html><body></body></html>')
|
||||
mock_get.side_effect = [half_redir, no_hcard]
|
||||
check(False, False)
|
||||
check(False, False, None)
|
||||
|
||||
# redirect works, non-representative h-card
|
||||
full_redir = requests_response(
|
||||
status=302, allow_redirects=False,
|
||||
redirected_url='http://localhost/.well-known/webfinger?resource=acct:y.z@y.z')
|
||||
bad_hcard = requests_response(
|
||||
'<html><body><a class="h-card u-url" href="https://a.b/">me</a></body></html>',
|
||||
'<html><body><a class="h-card u-url" href="https://a.b/">acct:me@y.z</a></body></html>',
|
||||
url='https://y.z/',
|
||||
)
|
||||
mock_get.side_effect = [full_redir, bad_hcard]
|
||||
check(True, False)
|
||||
check(True, False, None)
|
||||
|
||||
# both work
|
||||
hcard = requests_response(
|
||||
'<html><body><a class="h-card u-url" href="/">me</a></body></html>',
|
||||
hcard = requests_response("""
|
||||
<html><body class="h-card">
|
||||
<a class="u-url p-name" href="/">me</a>
|
||||
<a class="u-url" href="acct:myself@y.z">Masto</a>
|
||||
</body></html>""",
|
||||
url='https://y.z/',
|
||||
)
|
||||
mock_get.side_effect = [full_redir, hcard]
|
||||
check(True, True)
|
||||
check(True, True, {
|
||||
'type': 'Person',
|
||||
'name': 'me',
|
||||
'url': 'http://localhost/r/https://y.z/',
|
||||
'preferredUsername': 'myself',
|
||||
})
|
||||
|
||||
# reset
|
||||
mock_get.side_effect = [empty, empty]
|
||||
check(False, False)
|
||||
check(False, False, None)
|
||||
|
||||
|
||||
class ActivityTest(testutil.TestCase):
|
||||
|
|
10
webfinger.py
10
webfinger.py
|
@ -64,14 +64,8 @@ class Actor(flask_util.XrdOrJrd):
|
|||
urls = util.dedupe_urls(props.get('url', []) + [resp.url])
|
||||
canonical_url = urls[0]
|
||||
|
||||
acct = f'{domain}@{domain}'
|
||||
for url in urls:
|
||||
if url.startswith('acct:'):
|
||||
urluser, urldomain = util.parse_acct_uri(url)
|
||||
if urldomain == domain:
|
||||
acct = f'{urluser}@{domain}'
|
||||
logger.info(f'Found custom username: acct:{acct}')
|
||||
break
|
||||
username = common.get_username(domain, urls)
|
||||
acct = f'{username}@{domain}'
|
||||
|
||||
# discover atom feed, if any
|
||||
atom = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
|
||||
|
|
Ładowanie…
Reference in New Issue