kopia lustrzana https://github.com/snarfed/bridgy-fed
refactor follow/unfollow web UI to use Protocol.receive
rodzic
4d732e5d3e
commit
afc2511e62
56
follow.py
56
follow.py
|
@ -7,7 +7,7 @@
|
|||
import logging
|
||||
|
||||
from flask import g, redirect, request, session
|
||||
from granary import as1, as2
|
||||
from granary import as1
|
||||
from oauth_dropins import indieauth
|
||||
from oauth_dropins.webutil import util
|
||||
from oauth_dropins.webutil.flask_util import error, flash
|
||||
|
@ -110,30 +110,24 @@ class FollowCallback(indieauth.Callback):
|
|||
return redirect(user.user_page_path('following'))
|
||||
|
||||
followee_id = followee.as1.get('id')
|
||||
followee_as2 = ActivityPub.convert(followee)
|
||||
inbox = followee_as2.get('inbox')
|
||||
if not followee_id or not inbox:
|
||||
flash(f"AS2 profile {as2_url} missing id or inbox")
|
||||
return redirect(user.user_page_path('following'))
|
||||
|
||||
timestamp = NOW.replace(microsecond=0, tzinfo=None).isoformat()
|
||||
follow_id = common.host_url(user.user_page_path(f'following#{timestamp}-{addr}'))
|
||||
follow_as2 = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'type': 'Follow',
|
||||
follow_as1 = {
|
||||
'objectType': 'activity',
|
||||
'verb': 'follow',
|
||||
'id': follow_id,
|
||||
'actor': user.key.id(),
|
||||
'object': followee_id,
|
||||
'actor': user.id_as(ActivityPub),
|
||||
'to': [as2.PUBLIC_AUDIENCE],
|
||||
}
|
||||
followee_user = ActivityPub.get_or_create(followee_id, obj=followee)
|
||||
follow_obj = Object(id=follow_id, users=[user.key, followee_user.key],
|
||||
labels=['user'], source_protocol='ui', status='complete',
|
||||
as2=follow_as2)
|
||||
ActivityPub.send(follow_obj, inbox, from_user=user)
|
||||
follow_obj = Object(id=follow_id, our_as1=follow_as1, source_protocol='ui',
|
||||
labels=['user'])
|
||||
|
||||
Follower.get_or_create(from_=user, to=followee_user, status='active',
|
||||
follow=follow_obj.key)
|
||||
resp = Web.receive(follow_obj, authed_as=domain)
|
||||
logger.info(f'Web.receive returned {resp}')
|
||||
|
||||
follow_obj = follow_obj.key.get()
|
||||
follow_obj.source_protocol = 'ui'
|
||||
follow_obj.put()
|
||||
|
||||
url = as1.get_url(followee.as1) or followee_id
|
||||
|
@ -197,31 +191,29 @@ class UnfollowCallback(indieauth.Callback):
|
|||
followee.put()
|
||||
|
||||
# TODO(#529): generalize
|
||||
inbox = ActivityPub.convert(followee.obj).get('inbox')
|
||||
if not inbox:
|
||||
flash(f"AS2 profile {followee_id} missing inbox")
|
||||
return redirect(user.user_page_path('following'))
|
||||
|
||||
timestamp = NOW.replace(microsecond=0, tzinfo=None).isoformat()
|
||||
unfollow_id = common.host_url(user.user_page_path(f'following#undo-{timestamp}-{followee_id}'))
|
||||
unfollow_as2 = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'type': 'Undo',
|
||||
unfollow_as1 = {
|
||||
'objectType': 'activity',
|
||||
'verb': 'stop-following',
|
||||
'id': unfollow_id,
|
||||
'actor': user.id_as(ActivityPub),
|
||||
'object': follower.follow.get().as2 if follower.follow else None,
|
||||
'actor': user.key.id(),
|
||||
'object': followee.key.id(),
|
||||
}
|
||||
|
||||
# don't include the followee User who's being unfollowed in the users
|
||||
# property, since we don't want to notify or show them. (standard social
|
||||
# network etiquette.)
|
||||
obj = Object(id=unfollow_id, users=[user.key], labels=['user'],
|
||||
source_protocol='ui', status='complete', as2=unfollow_as2)
|
||||
ActivityPub.send(obj, inbox, from_user=user)
|
||||
follow_obj = Object(id=unfollow_id, users=[user.key], labels=['user'],
|
||||
source_protocol='ui', our_as1=unfollow_as1)
|
||||
resp = Web.receive(follow_obj, authed_as=domain)
|
||||
|
||||
follower.status = 'inactive'
|
||||
follower.put()
|
||||
obj.put()
|
||||
|
||||
follow_obj = follow_obj.key.get()
|
||||
follow_obj.source_protocol = 'ui'
|
||||
follow_obj.put()
|
||||
|
||||
link = common.pretty_link(as1.get_url(followee.obj.as1) or followee_id)
|
||||
flash(f'Unfollowed {link}.')
|
||||
|
|
|
@ -14,6 +14,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads
|
|||
from .testutil import Fake, TestCase
|
||||
|
||||
from activitypub import ActivityPub
|
||||
from common import unwrap
|
||||
from models import Follower, Object
|
||||
from web import Web
|
||||
|
||||
|
@ -41,20 +42,21 @@ FOLLOWEE = {
|
|||
FOLLOW_ADDRESS = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'type': 'Follow',
|
||||
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-@foo@bar',
|
||||
'id': f'http://localhost/r/alice.com/following#2022-01-02T03:04:05-@foo@bar',
|
||||
'actor': 'http://localhost/alice.com',
|
||||
'object': FOLLOWEE['id'],
|
||||
'to': [as2.PUBLIC_AUDIENCE],
|
||||
}
|
||||
FOLLOW_URL = copy.deepcopy(FOLLOW_ADDRESS)
|
||||
FOLLOW_URL['id'] = f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
||||
FOLLOW_URL['id'] = f'http://localhost/r/alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
||||
UNDO_FOLLOW = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'type': 'Undo',
|
||||
'id': f'http://localhost/web/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||
'id': f'http://localhost/r/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||
'actor': 'http://localhost/alice.com',
|
||||
'object': FOLLOW_ADDRESS,
|
||||
'object': copy.deepcopy(FOLLOW_ADDRESS),
|
||||
}
|
||||
del UNDO_FOLLOW['object']['id']
|
||||
|
||||
|
||||
@patch('requests.get')
|
||||
|
@ -136,7 +138,7 @@ class FollowTest(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = self.make_user('alice.com', cls=Web)
|
||||
self.user = self.make_user('alice.com', cls=Web, obj_id='https://alice.com/')
|
||||
self.state = {
|
||||
'endpoint': 'http://auth/endpoint',
|
||||
'me': 'https://alice.com',
|
||||
|
@ -207,7 +209,7 @@ class FollowTest(TestCase):
|
|||
|
||||
follow_with_profile_link = {
|
||||
**FOLLOW_URL,
|
||||
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-https://bar/id',
|
||||
'id': f'http://localhost/r/alice.com/following#2022-01-02T03:04:05-https://bar/id',
|
||||
'object': 'https://bar/id',
|
||||
}
|
||||
self.check('https://bar/id', resp, follow_with_profile_link, mock_get,
|
||||
|
@ -273,12 +275,17 @@ class FollowTest(TestCase):
|
|||
followers,
|
||||
ignore=['created', 'updated'])
|
||||
|
||||
expected_follow_as1 = as2.to_as1(unwrap(expected_follow))
|
||||
del expected_follow_as1['to']
|
||||
self.assert_object(follow_id,
|
||||
users=[self.user.key, followee],
|
||||
users=[self.user.key],
|
||||
notify=[followee],
|
||||
labels=['user', 'activity'],
|
||||
status='complete',
|
||||
source_protocol='ui',
|
||||
as2=expected_follow)
|
||||
our_as1=expected_follow_as1,
|
||||
delivered=['http://bar/inbox'],
|
||||
delivered_protocol='activitypub')
|
||||
|
||||
self.assertEqual('https://alice.com', session['indieauthed-me'])
|
||||
|
||||
|
@ -290,7 +297,8 @@ class FollowTest(TestCase):
|
|||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
def test_callback_user_use_instead(self, mock_get, mock_post):
|
||||
user = self.make_user('www.alice.com', cls=Web)
|
||||
user = self.make_user('www.alice.com', cls=Web,
|
||||
obj_id='https://www.alice.com/')
|
||||
self.user.use_instead = user.key
|
||||
self.user.put()
|
||||
|
||||
|
@ -310,16 +318,24 @@ class FollowTest(TestCase):
|
|||
self.assertEqual(302, resp.status_code)
|
||||
self.assertEqual('/web/www.alice.com/following', resp.headers['Location'])
|
||||
|
||||
id = 'http://localhost/web/www.alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
||||
expected_follow = {
|
||||
id = 'www.alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
||||
expected_follow_as1 = as2.to_as1({
|
||||
**FOLLOW_URL,
|
||||
'id': id,
|
||||
'actor': 'http://localhost/www.alice.com',
|
||||
}
|
||||
'actor': 'https://www.alice.com/',
|
||||
})
|
||||
del expected_follow_as1['to']
|
||||
followee = ActivityPub(id='https://bar/id').key
|
||||
follow_obj = self.assert_object(
|
||||
id, users=[user.key, followee], status='complete',
|
||||
labels=['user', 'activity'], source_protocol='ui', as2=expected_follow)
|
||||
f'http://localhost/web/{id}',
|
||||
users=[user.key],
|
||||
notify=[followee],
|
||||
status='complete',
|
||||
labels=['user', 'activity'],
|
||||
source_protocol='ui',
|
||||
our_as1=expected_follow_as1,
|
||||
delivered=['http://bar/inbox'],
|
||||
delivered_protocol='activitypub')
|
||||
|
||||
followers = Follower.query().fetch()
|
||||
self.assert_entities_equal(
|
||||
|
@ -466,7 +482,10 @@ class UnfollowTest(TestCase):
|
|||
|
||||
inbox_args, inbox_kwargs = mock_post.call_args
|
||||
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
|
||||
self.assert_equals({
|
||||
**expected_undo,
|
||||
'to': [as2.PUBLIC_AUDIENCE],
|
||||
}, json_loads(inbox_kwargs['data']))
|
||||
|
||||
# check that we signed with the follower's key
|
||||
sig_template = inbox_kwargs['auth'].header_signer.signature_template
|
||||
|
@ -479,8 +498,14 @@ class UnfollowTest(TestCase):
|
|||
|
||||
self.assert_object(
|
||||
'http://localhost/web/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||
users=[self.user.key], status='complete', source_protocol='ui',
|
||||
labels=['user', 'activity'], as2=expected_undo, as1=as2.to_as1(expected_undo))
|
||||
users=[self.user.key],
|
||||
notify=[ActivityPub(id='https://bar/id').key],
|
||||
status='complete',
|
||||
source_protocol='ui',
|
||||
labels=['user', 'activity'],
|
||||
our_as1=unwrap(as2.to_as1(expected_undo)),
|
||||
delivered=['http://bar/inbox'],
|
||||
delivered_protocol='activitypub')
|
||||
|
||||
self.assertEqual('https://alice.com', session['indieauthed-me'])
|
||||
|
||||
|
@ -513,25 +538,39 @@ class UnfollowTest(TestCase):
|
|||
self.assertEqual(302, resp.status_code)
|
||||
self.assertEqual('/web/www.alice.com/following', resp.headers['Location'])
|
||||
|
||||
id = 'http://localhost/web/www.alice.com/following#undo-2022-01-02T03:04:05-https://bar/id'
|
||||
id = 'http://localhost/r/www.alice.com/following#undo-2022-01-02T03:04:05-https://bar/id'
|
||||
expected_undo = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'type': 'Undo',
|
||||
'id': id,
|
||||
'actor': 'http://localhost/www.alice.com',
|
||||
'object': FOLLOW_ADDRESS,
|
||||
'object': {
|
||||
**FOLLOW_ADDRESS,
|
||||
'actor': 'http://localhost/www.alice.com',
|
||||
},
|
||||
}
|
||||
del expected_undo['object']['id']
|
||||
|
||||
inbox_args, inbox_kwargs = mock_post.call_args_list[1]
|
||||
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
|
||||
self.assert_equals({
|
||||
**expected_undo,
|
||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
}, json_loads(inbox_kwargs['data']))
|
||||
|
||||
follower = Follower.query().get()
|
||||
self.assertEqual('inactive', follower.status)
|
||||
|
||||
self.assert_object(id, users=[user.key], status='complete',
|
||||
source_protocol='ui', labels=['user', 'activity'],
|
||||
as2=expected_undo, as1=as2.to_as1(expected_undo))
|
||||
self.assert_object(
|
||||
'http://localhost/web/www.alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||
users=[user.key],
|
||||
notify=[ActivityPub(id='https://bar/id').key],
|
||||
status='complete',
|
||||
source_protocol='ui',
|
||||
labels=['user', 'activity'],
|
||||
our_as1=unwrap(as2.to_as1(expected_undo)),
|
||||
delivered=['http://bar/inbox'],
|
||||
delivered_protocol='activitypub')
|
||||
|
||||
def test_callback_composite_url(self, mock_get, mock_post):
|
||||
follower = self.follower.to.get().obj
|
||||
|
|
Ładowanie…
Reference in New Issue