Skip to content

Commit

Permalink
when someone follows the bot, but they're ineligible, reject their fo…
Browse files Browse the repository at this point in the history
  • Loading branch information
snarfed committed Feb 6, 2025
1 parent 1be3543 commit 3aa1b4b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 32 deletions.
38 changes: 24 additions & 14 deletions protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,14 @@ def receive(from_cls, obj, authed_as=None, internal=False, received_at=None):
if proto := Protocol.for_bridgy_subdomain(inner_obj_id):
# follow of one of our protocol bot users; enable that protocol.
# fall through so that we send an accept.
from_user.enable_protocol(proto)
try:
from_user.enable_protocol(proto)
except ErrorButDoNotRetryTask:
from web import Web
bot = Web.get_by_id(proto.bot_user_id())
from_cls.respond_to_follow('reject', follower=from_user,
followee=bot, follow=obj)
raise
proto.bot_follow(from_user)

from_cls.handle_follow(obj)
Expand Down Expand Up @@ -1167,34 +1174,37 @@ def handle_follow(from_cls, obj):
follower_obj = Follower.get_or_create(to=to_user, from_=from_user,
follow=obj.key, status='active')
obj.add('notify', to_key)
from_cls.maybe_accept_follow(follower=from_user, followee=to_user,
follow=obj)
from_cls.respond_to_follow('accept', follower=from_user,
followee=to_user, follow=obj)

@classmethod
def maybe_accept_follow(_, follower, followee, follow):
"""Sends an accept activity for a follow.
def respond_to_follow(_, verb, follower, followee, follow):
"""Sends an accept or reject activity for a follow.
...if the follower protocol handles accepts. Otherwise, does nothing.
...if the follower's protocol supports accepts/rejects. Otherwise, does
nothing.
Args:
follower: :class:`models.User`
followee: :class:`models.User`
follow: :class:`models.Object`
verb (str): ``accept`` or ``reject``
follower (models.User)
followee (models.User)
follow (models.Object)
"""
if 'accept' not in follower.SUPPORTED_AS1_TYPES:
assert verb in ('accept', 'reject')
if verb not in follower.SUPPORTED_AS1_TYPES:
return

target = follower.target_for(follower.obj)
if not target:
error(f"Couldn't find delivery target for follower {follower.key.id()}")

# send accept. note that this is one accept for the whole
# follow, even if it has multiple followees!
id = f'{followee.key.id()}/followers#accept-{follow.key.id()}'
# send. note that this is one response for the whole follow, even if it
# has multiple followees!
id = f'{followee.key.id()}/followers#{verb}-{follow.key.id()}'
accept = {
'id': id,
'objectType': 'activity',
'verb': 'accept',
'verb': verb,
'actor': followee.key.id(),
'object': follow.as1,
}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_dms.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ def assert_replied(self, *args, **kwargs):
kwargs.setdefault('in_reply_to', 'efake:dm')
self.assert_sent(*args, **kwargs)

def assert_sent(self, from_cls, tos, type, text, in_reply_to=None):
def assert_sent(self, from_cls, tos, type, text, in_reply_to=None, strict=True):
if not isinstance(tos, list):
tos = [tos]

from_id = f'{from_cls.ABBREV}.brid.gy'
for expected, (target, activity) in zip(tos, tos[-1].sent, strict=True):
for expected, (target, activity) in zip(tos, tos[-1].sent, strict=strict):
id = expected.key.id()
self.assertEqual(f'{id}:target', target)
content = activity['object'].pop('content')
Expand Down
66 changes: 50 additions & 16 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -3002,21 +3002,38 @@ def test_follow_bot_user_spam_filter_doesnt_enable(self):
user = self.make_user('efake:user', cls=ExplicitFake)
ExplicitFake.fetchable = {'efake:user': {'id': 'efake:user'}}

follow_as1 = {
'objectType': 'activity',
'verb': 'follow',
'id': 'efake:follow',
'actor': 'efake:user',
'object': 'efake:bot',
}
with self.assertRaises(ErrorButDoNotRetryTask):
_, code = ExplicitFake.receive_as1({
'objectType': 'activity',
'verb': 'follow',
'id': 'efake:follow',
'actor': 'efake:user',
'object': 'efake:bot',
})
_, code = ExplicitFake.receive_as1(follow_as1)

user = user.key.get()
self.assertEqual('requires-name', user.status)
self.assertFalse(user.is_enabled(Fake))
self.assertEqual(['efake:user'], ExplicitFake.fetched)

test_dms.DmsTest().assert_sent(Fake, user, 'requires-name', "Hi! Your account isn't eligible for bridging yet because you haven't set a profile name that's different from your username.")
self.assertEqual(2, len(ExplicitFake.sent))
test_dms.DmsTest().assert_sent(
Fake, user, 'requires-name',
"Hi! Your account isn't eligible for bridging yet because you haven't set a profile name that's different from your username.",
strict=False)
self.assertEqual(('efake:user:target', {
'objectType': 'activity',
'verb': 'reject',
'id': 'fa.brid.gy/followers#reject-efake:follow',
'actor': 'fa.brid.gy',
'object': {
**follow_as1,
'actor': {'id': 'efake:user'},
'object': 'fa.brid.gy',
},
}), ExplicitFake.sent[1])


@patch.object(Fake, 'owns_handle', return_value=False)
@patch.object(ExplicitFake, 'owns_handle', return_value=True)
Expand All @@ -3027,18 +3044,35 @@ def test_follow_bot_user_unsupported_handle(self, _, __):
user = self.make_user('efake:user-nope', cls=ExplicitFake)
ExplicitFake.fetchable = {'efake:user-nope': {'id': 'efake:user-nope'}}

follow_as1 = {
'objectType': 'activity',
'verb': 'follow',
'id': 'efake:follow',
'actor': 'efake:user-nope',
'object': 'efake:bot',
}
with self.assertRaises(ErrorButDoNotRetryTask):
_, code = ExplicitFake.receive_as1({
'objectType': 'activity',
'verb': 'follow',
'id': 'efake:follow',
'actor': 'efake:user-nope',
'object': 'efake:bot',
})
_, code = ExplicitFake.receive_as1(follow_as1)

user = user.key.get()
self.assertFalse(user.is_enabled(Fake))
test_dms.DmsTest().assert_sent(Fake, user, 'unsupported-handle-fa', "Hi! Your account isn't eligible for bridging yet because efake:handle:user-nope translated to fake-phrase is fake:handle:efake:handle:user-nope, which isn't supported there.")

self.assertEqual(2, len(ExplicitFake.sent))
test_dms.DmsTest().assert_sent(
Fake, user, 'unsupported-handle-fa',
"Hi! Your account isn't eligible for bridging yet because efake:handle:user-nope translated to fake-phrase is fake:handle:efake:handle:user-nope, which isn't supported there.",
strict=False)
self.assertEqual(('efake:user-nope:target', {
'objectType': 'activity',
'verb': 'reject',
'id': 'fa.brid.gy/followers#reject-efake:follow',
'actor': 'fa.brid.gy',
'object': {
**follow_as1,
'actor': {'id': 'efake:user-nope'},
'object': 'fa.brid.gy',
},
}), ExplicitFake.sent[1])

def test_block_then_follow_protocol_user_recreates_copy(self):
# bot user
Expand Down

0 comments on commit 3aa1b4b

Please sign in to comment.