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

Commit af7db19

Browse files
authored
Uniformize spam-checker API, part 3: Expand check_event_for_spam with the ability to return additional fields (#12846)
Signed-off-by: David Teller <davidt@element.io>
1 parent 1fd1856 commit af7db19

File tree

5 files changed

+43
-17
lines changed

5 files changed

+43
-17
lines changed

changelog.d/12808.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update to `check_event_for_spam`. Deprecate the current callback signature, replace it with a new signature that is both less ambiguous (replacing booleans with explicit allow/block) and more powerful (ability to return explicit error codes).

changelog.d/12846.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Experimental: expand `check_event_for_spam` with ability to return additional fields. This enables spam-checker implementations to experiment with mechanisms to give users more information about why they are blocked and whether any action is needed from them to be unblocked.

synapse/api/errors.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,13 @@ class SynapseError(CodeMessageException):
146146
errcode: Matrix error code e.g 'M_FORBIDDEN'
147147
"""
148148

149-
def __init__(self, code: int, msg: str, errcode: str = Codes.UNKNOWN):
149+
def __init__(
150+
self,
151+
code: int,
152+
msg: str,
153+
errcode: str = Codes.UNKNOWN,
154+
additional_fields: Optional[Dict] = None,
155+
):
150156
"""Constructs a synapse error.
151157
152158
Args:
@@ -156,9 +162,13 @@ def __init__(self, code: int, msg: str, errcode: str = Codes.UNKNOWN):
156162
"""
157163
super().__init__(code, msg)
158164
self.errcode = errcode
165+
if additional_fields is None:
166+
self._additional_fields: Dict = {}
167+
else:
168+
self._additional_fields = dict(additional_fields)
159169

160170
def error_dict(self) -> "JsonDict":
161-
return cs_error(self.msg, self.errcode)
171+
return cs_error(self.msg, self.errcode, **self._additional_fields)
162172

163173

164174
class InvalidAPICallError(SynapseError):
@@ -183,14 +193,7 @@ def __init__(
183193
errcode: str = Codes.UNKNOWN,
184194
additional_fields: Optional[Dict] = None,
185195
):
186-
super().__init__(code, msg, errcode)
187-
if additional_fields is None:
188-
self._additional_fields: Dict = {}
189-
else:
190-
self._additional_fields = dict(additional_fields)
191-
192-
def error_dict(self) -> "JsonDict":
193-
return cs_error(self.msg, self.errcode, **self._additional_fields)
196+
super().__init__(code, msg, errcode, additional_fields)
194197

195198

196199
class ConsentNotGivenError(SynapseError):

synapse/events/spamcheck.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
Awaitable,
2222
Callable,
2323
Collection,
24+
Dict,
2425
List,
2526
Optional,
2627
Tuple,
@@ -41,13 +42,17 @@
4142

4243
logger = logging.getLogger(__name__)
4344

44-
4545
CHECK_EVENT_FOR_SPAM_CALLBACK = Callable[
4646
["synapse.events.EventBase"],
4747
Awaitable[
4848
Union[
4949
Allow,
5050
Codes,
51+
# Highly experimental, not officially part of the spamchecker API, may
52+
# disappear without warning depending on the results of ongoing
53+
# experiments.
54+
# Use this to return additional information as part of an error.
55+
Tuple[Codes, Dict],
5156
# Deprecated
5257
bool,
5358
# Deprecated
@@ -270,7 +275,7 @@ def register_callbacks(
270275

271276
async def check_event_for_spam(
272277
self, event: "synapse.events.EventBase"
273-
) -> Union[Decision, str]:
278+
) -> Union[Decision, Tuple[Codes, Dict], str]:
274279
"""Checks if a given event is considered "spammy" by this server.
275280
276281
If the server considers an event spammy, then it will be rejected if
@@ -293,9 +298,9 @@ async def check_event_for_spam(
293298
with Measure(
294299
self.clock, "{}.{}".format(callback.__module__, callback.__qualname__)
295300
):
296-
res: Union[Decision, str, bool] = await delay_cancellation(
297-
callback(event)
298-
)
301+
res: Union[
302+
Decision, Tuple[Codes, Dict], str, bool
303+
] = await delay_cancellation(callback(event))
299304
if res is False or res is Allow.ALLOW:
300305
# This spam-checker accepts the event.
301306
# Other spam-checkers may reject it, though.
@@ -305,8 +310,9 @@ async def check_event_for_spam(
305310
# return value `True`
306311
return Codes.FORBIDDEN
307312
else:
308-
# This spam-checker rejects the event either with a `str`
309-
# or with a `Codes`. In either case, we stop here.
313+
# This spam-checker rejects the event either with a `str`,
314+
# with a `Codes` or with a `Tuple[Codes, Dict]`. In either
315+
# case, we stop here.
310316
return res
311317

312318
# No spam-checker has rejected the event, let it pass.

synapse/handlers/message.py

+15
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,21 @@ async def create_and_send_nonmember_event(
895895

896896
spam_check = await self.spam_checker.check_event_for_spam(event)
897897
if spam_check is not synapse.spam_checker_api.Allow.ALLOW:
898+
if isinstance(spam_check, tuple):
899+
try:
900+
[code, dict] = spam_check
901+
raise SynapseError(
902+
403,
903+
"This message had been rejected as probable spam",
904+
code,
905+
dict,
906+
)
907+
except ValueError:
908+
logger.error(
909+
"Spam-check module returned invalid error value. Expecting [code, dict], got %s",
910+
spam_check,
911+
)
912+
spam_check = Codes.FORBIDDEN
898913
raise SynapseError(
899914
403, "This message had been rejected as probable spam", spam_check
900915
)

0 commit comments

Comments
 (0)