From f357ea1698998cdc874f37a762a04165fc29e06d Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Sun, 21 Apr 2024 08:36:03 -0700 Subject: [PATCH] ActivityPub: accept non-public DMs to protocol bot users for #880 --- activitypub.py | 20 +++++++++----------- common.py | 2 ++ protocol.py | 22 +++++++++++----------- tests/test_activitypub.py | 22 +++++++++++++++++++++- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/activitypub.py b/activitypub.py index 41ef479..802d873 100644 --- a/activitypub.py +++ b/activitypub.py @@ -57,7 +57,9 @@ WEB_OPT_OUT_DOMAINS = None FEDI_URL_RE = re.compile(r'https://[^/]+/(@|users/)([^/@]+)(@[^/@]+)?(/(?:statuses/)?[0-9]+)?') -_BOT_ACTOR_IDS = None +# can't use translate_user_id because Web.owns_id checks valid_domain, which +# doesn't allow our protocol subdomains +BOT_ACTOR_IDS = [f'https://{domain}/{domain}' for domain in PROTOCOL_DOMAINS] def instance_actor(): @@ -68,15 +70,6 @@ def instance_actor(): return _INSTANCE_ACTOR -def bot_actor_ids(): - global _BOT_ACTOR_IDS - if _BOT_ACTOR_IDS is None: - from activitypub import ActivityPub - _BOT_ACTOR_IDS = [translate_user_id(id=domain, from_=Web, to=ActivityPub) - for domain in PROTOCOL_DOMAINS] - return _BOT_ACTOR_IDS - - class ActivityPub(User, Protocol): """ActivityPub protocol class. @@ -939,7 +932,12 @@ def inbox(protocol=None, id=None): # those as explicitly public. Use as2's is_public instead of as1's because # as1's interprets unlisted as true. # TODO: move this to Protocol - if type == 'Create' and not as2.is_public(activity, unlisted=False): + object = as1.get_object(activity) + to_cc = set(as1.get_ids(object, 'to') + as1.get_ids(activity, 'cc') + + as1.get_ids(object, 'to') + as1.get_ids(object, 'cc')) + if (type == 'Create' and not as2.is_public(activity, unlisted=False) + # DM to one of our protocol bot users + and not (len(to_cc) == 1 and to_cc.pop() in BOT_ACTOR_IDS)): logger.info('Dropping non-public activity') return 'OK' diff --git a/common.py b/common.py index 9c28b8c..d5bea16 100644 --- a/common.py +++ b/common.py @@ -42,7 +42,9 @@ PROTOCOL_DOMAINS = ( 'atproto.brid.gy', 'bluesky.brid.gy', 'bsky.brid.gy', + 'eefake.brid.gy', 'fa.brid.gy', + 'other.brid.gy', 'nostr.brid.gy', 'web.brid.gy', ) diff --git a/protocol.py b/protocol.py index 5c80cc6..0902925 100644 --- a/protocol.py +++ b/protocol.py @@ -805,18 +805,18 @@ class Protocol: return 'OK', 200 elif obj.type == 'post': - to_cc = (util.get_list(inner_obj_as1, 'to') - + util.get_list(inner_obj_as1, 'cc')) - if len(to_cc) == 1 and to_cc[0] in PROTOCOL_DOMAINS: - content = inner_obj_as1.get('content').strip().lower() - logger.info(f'DM to bot user {to_cc}: {content}') + to_cc = (as1.get_ids(inner_obj_as1, 'to') + + as1.get_ids(inner_obj_as1, 'cc')) + content = inner_obj_as1.get('content', '').strip().lower() + logger.info(f'got DM to {to_cc}: {content}') + if len(to_cc) == 1: proto = Protocol.for_bridgy_subdomain(to_cc[0]) - assert proto - if content in ('yes', 'ok'): - from_user.enable_protocol(proto) - elif content == 'no': - from_user.disable_protocol(proto) - return 'OK', 200 + if proto: + if content in ('yes', 'ok'): + from_user.enable_protocol(proto) + elif content == 'no': + from_user.disable_protocol(proto) + return 'OK', 200 # fetch actor if necessary if actor and actor.keys() == set(['id']): diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index 93174ac..9f44819 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -20,7 +20,7 @@ from werkzeug.exceptions import BadGateway, BadRequest # import first so that Fake is defined before URL routes are registered from . import testutil -from .testutil import Fake, TestCase +from .testutil import ExplicitEnableFake, Fake, TestCase import activitypub from activitypub import ( @@ -847,6 +847,26 @@ class ActivityPubTest(TestCase): self.assertIsNone(Object.get_by_id(not_public['id'])) self.assertIsNone(Object.get_by_id(not_public['object']['id'])) + def test_inbox_dm_yes_to_bot_user_enables_protocol(self, *mocks): + user = self.make_user(ACTOR['id'], cls=ActivityPub) + self.assertFalse(ActivityPub.is_enabled_to(ExplicitEnableFake, user)) + + got = self.post('/ap/sharedInbox', json={ + 'type': 'Create', + 'id': 'https://mas.to/dm#create', + 'to': ['https://eefake.brid.gy/eefake.brid.gy'], + 'object': { + 'type': 'Note', + 'id': 'https://mas.to/dm', + 'attributedTo': ACTOR['id'], + 'to': ['https://eefake.brid.gy/eefake.brid.gy'], + 'content': 'yes', + }, + }) + self.assertEqual(200, got.status_code, got.get_data(as_text=True)) + user = user.key.get() + self.assertTrue(ActivityPub.is_enabled_to(ExplicitEnableFake, user)) + def test_inbox_actor_blocklisted(self, mock_head, mock_get, mock_post): got = self.post('/ap/sharedInbox', json={ 'type': 'Delete',