kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
a222d5d1c5
commit
0e7728b8c2
2
app.py
2
app.py
|
@ -35,4 +35,4 @@ cache = Cache(app)
|
|||
util.set_user_agent('Bridgy Fed (https://fed.brid.gy/)')
|
||||
|
||||
|
||||
import activitypub, add_webmention, pages, redirect, render, salmon, superfeedr, webfinger, webmention
|
||||
import activitypub, add_webmention, follow, pages, redirect, render, salmon, superfeedr, webfinger, webmention
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
"""Remote follow handler.
|
||||
|
||||
https://github.com/snarfed/bridgy-fed/issues/60
|
||||
https://socialhub.activitypub.rocks/t/what-is-the-current-spec-for-remote-follow/2020
|
||||
https://www.rfc-editor.org/rfc/rfc7033
|
||||
"""
|
||||
import logging
|
||||
import urllib.parse
|
||||
|
||||
from flask import redirect, request
|
||||
from oauth_dropins.webutil import flask_util, util
|
||||
from oauth_dropins.webutil.flask_util import error, flash
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
from app import app
|
||||
import common
|
||||
from models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe'
|
||||
|
||||
|
||||
@app.post('/follow')
|
||||
def remote_follow():
|
||||
"""Discovers and redirects to a remote follow page for a given user."""
|
||||
logger.info(f'Got: {request.values}')
|
||||
|
||||
domain = request.values['domain']
|
||||
user = User.get_by_id(domain)
|
||||
if not user:
|
||||
error(f'No Bridgy Fed user found for domain {domain}')
|
||||
|
||||
addr = request.values['address'].strip().strip('@')
|
||||
split = addr.split('@')
|
||||
if len(split) == 2:
|
||||
addr_domain = split[1]
|
||||
resource = f'acct:{addr}'
|
||||
elif addr.startswith('http://') or addr.startswith('https://'):
|
||||
addr_domain = util.domain_from_link(addr, minimize=False)
|
||||
resource = addr
|
||||
else:
|
||||
flash('Enter your fediverse address in @user@domain.com format')
|
||||
return redirect(f'/user/{domain}')
|
||||
|
||||
# look up remote user via webfinger
|
||||
try:
|
||||
resp = util.requests_get(
|
||||
f'https://{addr_domain}/.well-known/webfinger?resource={resource}')
|
||||
except BaseException as e:
|
||||
if util.is_connection_failure(e):
|
||||
flash(f"Couldn't connect to {addr_domain}")
|
||||
return redirect(f'/user/{domain}')
|
||||
raise
|
||||
|
||||
if not resp.ok:
|
||||
flash(f'WebFinger on {addr_domain} returned HTTP {resp.status_code}')
|
||||
return redirect(f'/user/{domain}')
|
||||
|
||||
# find remote follow link and redirect
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError as e:
|
||||
logger.warning(f'Got {e}', exc_info=True)
|
||||
flash(f'WebFinger on {domain} returned non-JSON')
|
||||
return redirect(f'/user/{domain}')
|
||||
|
||||
logger.info(f'Got: {json_dumps(data, indent=2)}')
|
||||
for link in data.get('links', []):
|
||||
if link.get('rel') == SUBSCRIBE_LINK_REL:
|
||||
template = link.get('template')
|
||||
if template and '{uri}' in template:
|
||||
return redirect(template.replace('{uri}', user.address()))
|
||||
|
||||
flash(f"Couldn't find remote follow link for {addr}")
|
||||
return redirect(f'/user/{domain}')
|
|
@ -42,6 +42,18 @@
|
|||
· <a href="/{{ domain }}">ActivityPub</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<form method="post" action="/follow">
|
||||
<p>
|
||||
<label for="follow-address">Enter your fediverse address to follow:</label>
|
||||
<input id="follow-address" name="address" type="text" required
|
||||
placeholder="@user@domain.com" alt="fediverse address"></input>
|
||||
<input name="domain" type="hidden" value="{{ domain }}"></input>
|
||||
<button type="submit" class="btn btn-default">Follow</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row">Recent activity</div> -->
|
||||
|
||||
{% include "activities.html" %}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
"""Unit tests for follow.py.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from oauth_dropins.webutil.testutil import requests_response
|
||||
|
||||
import common
|
||||
from models import User
|
||||
from . import testutil
|
||||
|
||||
WEBFINGER = requests_response({
|
||||
'subject': 'acct:foo@bar',
|
||||
'aliases': [
|
||||
'https://bar/foo',
|
||||
],
|
||||
'links': [{
|
||||
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'template': 'https://bar/follow?uri={uri}'
|
||||
}],
|
||||
})
|
||||
|
||||
@patch('requests.get')
|
||||
class FollowTest(testutil.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
User.get_or_create('me')
|
||||
|
||||
def test_follow_no_domain(self, mock_get):
|
||||
got = self.client.post('/follow?address=@foo@bar')
|
||||
self.assertEqual(400, got.status_code)
|
||||
|
||||
def test_follow_no_address(self, mock_get):
|
||||
got = self.client.post('/follow?domain=baz.com')
|
||||
self.assertEqual(400, got.status_code)
|
||||
|
||||
def test_follow_no_user(self, mock_get):
|
||||
got = self.client.post('/follow?address=@foo@bar&domain=baz.com')
|
||||
self.assertEqual(400, got.status_code)
|
||||
|
||||
def test_follow(self, mock_get):
|
||||
mock_get.return_value = WEBFINGER
|
||||
got = self.client.post('/follow?address=@foo@bar&domain=me')
|
||||
self.assertEqual(302, got.status_code)
|
||||
self.assertEqual('https://bar/follow?uri=@me@me',
|
||||
got.headers['Location'])
|
||||
|
||||
mock_get.assert_has_calls((
|
||||
self.req('https://bar/.well-known/webfinger?resource=acct:foo@bar'),
|
||||
))
|
||||
|
||||
def test_follow_url(self, mock_get):
|
||||
mock_get.return_value = WEBFINGER
|
||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
||||
self.assertEqual(302, got.status_code)
|
||||
self.assertEqual('https://bar/follow?uri=@me@me', got.headers['Location'])
|
||||
|
||||
mock_get.assert_has_calls((
|
||||
self.req('https://bar/.well-known/webfinger?resource=https://bar/foo'),
|
||||
))
|
||||
|
||||
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
||||
mock_get.return_value = requests_response({
|
||||
'subject': 'acct:foo@bar',
|
||||
'links': [{'rel': 'other', 'template': 'meh'}],
|
||||
})
|
||||
|
||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
||||
self.assertEqual(302, got.status_code)
|
||||
self.assertEqual('/user/me', got.headers['Location'])
|
||||
|
||||
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
||||
mock_get.return_value = requests_response(status_code=500)
|
||||
|
||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
||||
self.assertEqual(302, got.status_code)
|
||||
self.assertEqual('/user/me', got.headers['Location'])
|
||||
|
||||
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
||||
mock_get.return_value = requests_response('<html>not json</html>')
|
||||
|
||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
||||
self.assertEqual(302, got.status_code)
|
||||
self.assertEqual('/user/me', got.headers['Location'])
|
Ładowanie…
Reference in New Issue