Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 2bec29c

Browse files
committed
Make check_event_for_spam async
Method `check_event_for_spam` in both `class SpamCheck` and in plug-in spam checkers is currently sync. This makes it impossible to write spam checkers that need to e.g. check with a database before responding. Signed-off-by: Your Name <davidt@element.io>
1 parent 9edff90 commit 2bec29c

File tree

5 files changed

+30
-14
lines changed

5 files changed

+30
-14
lines changed

changelog.d/8890.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Spam-checkers may now define their method `check_event_for_spam` as `async`. This will simplify the work of spam-checkers that need to e.g. request data from a database without blocking the server.

docs/spam_checker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ExampleSpamChecker:
3737
self.config = config
3838
self.api = api
3939

40-
def check_event_for_spam(self, foo):
40+
async def check_event_for_spam(self, foo):
4141
return False # allow all events
4242

4343
def user_may_invite(self, inviter_userid, invitee_userid, room_id):

synapse/events/spamcheck.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from synapse.spam_checker_api import RegistrationBehaviour
2121
from synapse.types import Collection
22+
from synapse.util.async_helpers import maybe_awaitable
2223

2324
if TYPE_CHECKING:
2425
import synapse.events
@@ -39,7 +40,7 @@ def __init__(self, hs: "synapse.server.HomeServer"):
3940
else:
4041
self.spam_checkers.append(module(config=config))
4142

42-
def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
43+
async def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
4344
"""Checks if a given event is considered "spammy" by this server.
4445
4546
If the server considers an event spammy, then it will be rejected if
@@ -53,7 +54,7 @@ def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
5354
True if the event is spammy.
5455
"""
5556
for spam_checker in self.spam_checkers:
56-
if spam_checker.check_event_for_spam(event):
57+
if await maybe_awaitable(spam_checker.check_event_for_spam(event)):
5758
return True
5859

5960
return False

synapse/federation/federation_base.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from typing import Iterable, List
1919

2020
from twisted.internet import defer
21-
from twisted.internet.defer import Deferred, DeferredList
21+
from twisted.internet.defer import Deferred, DeferredList, ensureDeferred
2222
from twisted.python.failure import Failure
2323

2424
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership
@@ -35,6 +35,7 @@
3535
make_deferred_yieldable,
3636
)
3737
from synapse.types import JsonDict, get_domain_from_id
38+
from synapse.util.async_helpers import maybe_awaitable
3839

3940
logger = logging.getLogger(__name__)
4041

@@ -74,11 +75,11 @@ def _check_sigs_and_hashes(
7475
* throws a SynapseError if the signature check failed.
7576
The deferreds run their callbacks in the sentinel
7677
"""
77-
deferreds = _check_sigs_on_pdus(self.keyring, room_version, pdus)
78+
initial_deferreds = _check_sigs_on_pdus(self.keyring, room_version, pdus)
7879

7980
ctx = current_context()
8081

81-
def callback(_, pdu: EventBase):
82+
async def callback(_, pdu: EventBase):
8283
with PreserveLoggingContext(ctx):
8384
if not check_event_content_hash(pdu):
8485
# let's try to distinguish between failures because the event was
@@ -105,7 +106,7 @@ def callback(_, pdu: EventBase):
105106
)
106107
return redacted_event
107108

108-
if self.spam_checker.check_event_for_spam(pdu):
109+
if await maybe_awaitable(self.spam_checker.check_event_for_spam(pdu)):
109110
logger.warning(
110111
"Event contains spam, redacting %s: %s",
111112
pdu.event_id,
@@ -125,12 +126,23 @@ def errback(failure: Failure, pdu: EventBase):
125126
)
126127
return failure
127128

128-
for deferred, pdu in zip(deferreds, pdus):
129-
deferred.addCallbacks(
130-
callback, errback, callbackArgs=[pdu], errbackArgs=[pdu]
131-
)
129+
# Here, we require a little gymnastics to return
130+
# deferreds that accept awaitables as callbacks
131+
deferred_with_callbacks = []
132+
for deferred, pdu in zip(initial_deferreds, pdus):
133+
134+
async def awaitable():
135+
result = None
136+
try:
137+
result = await deferred
138+
except Exception as e:
139+
return errback(e, pdu)
140+
else:
141+
return await callback(result, pdu)
142+
143+
deferred_with_callbacks.append(ensureDeferred(awaitable))
132144

133-
return deferreds
145+
return deferred_with_callbacks
134146

135147

136148
class PduToCheckSig(

synapse/handlers/message.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from synapse.storage.state import StateFilter
5252
from synapse.types import Requester, RoomAlias, StreamToken, UserID, create_requester
5353
from synapse.util import json_decoder, json_encoder
54-
from synapse.util.async_helpers import Linearizer
54+
from synapse.util.async_helpers import Linearizer, maybe_awaitable
5555
from synapse.util.metrics import measure_func
5656
from synapse.visibility import filter_events_for_client
5757

@@ -744,7 +744,9 @@ async def create_and_send_nonmember_event(
744744
event.sender,
745745
)
746746

747-
spam_error = self.spam_checker.check_event_for_spam(event)
747+
spam_error = await maybe_awaitable(
748+
self.spam_checker.check_event_for_spam(event)
749+
)
748750
if spam_error:
749751
if not isinstance(spam_error, str):
750752
spam_error = "Spam is not permitted here"

0 commit comments

Comments
 (0)