From d36885728f4daab4cc6dceae5cf2e41c55f4bf28 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Thu, 18 Apr 2024 15:56:40 -0700 Subject: [PATCH] Protocol.receive: blocking protocol user disables that protocol for #880 --- common.py | 11 +++++++++++ protocol.py | 28 +++++++++++++++++++++++++++- tests/test_protocol.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/common.py b/common.py index 2cf858e..35d397a 100644 --- a/common.py +++ b/common.py @@ -33,6 +33,8 @@ CONTENT_TYPE_HTML = 'text/html; charset=utf-8' # Protocol pairs that we currently support bridging between. Values must be # Protocol LABELs. Each pair must be lexicographically sorted! +# TODO: remove in favor of Protocol.DEFAULT_ENABLED_PROTOCOLS and +# User.enabled_protocols ENABLED_BRIDGES = frozenset(( ('activitypub', 'web'), ('atproto', 'web'), @@ -255,6 +257,15 @@ def add(seq, val): seq.append(val) +def remove(seq, val): + """Removes ``val`` to ``seq`` if seq contains it. + + Useful for treating repeated ndb properties like sets instead of lists. + """ + if val in seq: + seq.remove(val) + + def create_task(queue, delay=None, **params): """Adds a Cloud Tasks task. diff --git a/protocol.py b/protocol.py index 9ffb96d..4e6b8a8 100644 --- a/protocol.py +++ b/protocol.py @@ -20,7 +20,16 @@ from oauth_dropins.webutil.util import json_dumps, json_loads import werkzeug.exceptions import common -from common import add, DOMAIN_BLOCKLIST, DOMAIN_RE, DOMAINS, error, subdomain_wrap +from common import ( + add, + DOMAIN_BLOCKLIST, + DOMAIN_RE, + DOMAINS, + error, + PROTOCOL_DOMAINS, + remove, + subdomain_wrap, +) from flask_app import app from ids import translate_object_id, translate_user_id from models import Follower, get_originals, Object, PROTOCOLS, Target, User @@ -29,6 +38,7 @@ SUPPORTED_TYPES = ( 'accept', 'article', 'audio', + 'block', 'comment', 'delete', 'follow', @@ -783,6 +793,22 @@ class Protocol: # fall through to deliver to followers + elif obj.type == 'block': + proto = Protocol.for_bridgy_subdomain(inner_obj_id) + if not proto: + logger.info("Ignoring block, target isn't one of our protocol domains") + return 'OK', 200 + + @ndb.transactional() + def block(): + nonlocal from_user + from_user = from_user.key.get() + remove(from_user.enabled_protocols, proto.LABEL) + from_user.put() + + block() + return 'OK', 200 + # fetch actor if necessary if actor and actor.keys() == set(['id']): logger.info('Fetching actor so we have name, profile photo, etc') diff --git a/tests/test_protocol.py b/tests/test_protocol.py index c1f4a73..0ab7812 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -1779,6 +1779,34 @@ class ProtocolReceiveTest(TestCase): }], }, obj.key.get().our_as1) + def test_block_protocol_user_removes_from_enabled_protocols(self): + block = { + 'objectType': 'activity', + 'verb': 'block', + 'id': 'eefake:block', + 'actor': 'eefake:user', + 'object': 'fa.brid.gy', + } + + user = self.make_user('eefake:user', cls=ExplicitEnableFake) + self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user)) + + # protocol isn't enabled yet, block is a noop + self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(block)) + user = user.key.get() + self.assertEqual([], user.enabled_protocols) + + # enable protocol, now block should remove it + user.enabled_protocols = ['fake'] + user.put() + self.assertTrue(ExplicitEnableFake.is_enabled_to(Fake, user)) + + block['id'] += '2' + self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(block)) + user = user.key.get() + self.assertEqual([], user.enabled_protocols) + self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user)) + def test_receive_task_handler(self): note = { 'id': 'fake:post',