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>
This commit is contained in:
parent
1fd1856afc
commit
af7db19e1e
|
@ -0,0 +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).
|
|
@ -0,0 +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.
|
|
@ -146,7 +146,13 @@ class SynapseError(CodeMessageException):
|
|||
errcode: Matrix error code e.g 'M_FORBIDDEN'
|
||||
"""
|
||||
|
||||
def __init__(self, code: int, msg: str, errcode: str = Codes.UNKNOWN):
|
||||
def __init__(
|
||||
self,
|
||||
code: int,
|
||||
msg: str,
|
||||
errcode: str = Codes.UNKNOWN,
|
||||
additional_fields: Optional[Dict] = None,
|
||||
):
|
||||
"""Constructs a synapse error.
|
||||
|
||||
Args:
|
||||
|
@ -156,9 +162,13 @@ class SynapseError(CodeMessageException):
|
|||
"""
|
||||
super().__init__(code, msg)
|
||||
self.errcode = errcode
|
||||
if additional_fields is None:
|
||||
self._additional_fields: Dict = {}
|
||||
else:
|
||||
self._additional_fields = dict(additional_fields)
|
||||
|
||||
def error_dict(self) -> "JsonDict":
|
||||
return cs_error(self.msg, self.errcode)
|
||||
return cs_error(self.msg, self.errcode, **self._additional_fields)
|
||||
|
||||
|
||||
class InvalidAPICallError(SynapseError):
|
||||
|
@ -183,14 +193,7 @@ class ProxiedRequestError(SynapseError):
|
|||
errcode: str = Codes.UNKNOWN,
|
||||
additional_fields: Optional[Dict] = None,
|
||||
):
|
||||
super().__init__(code, msg, errcode)
|
||||
if additional_fields is None:
|
||||
self._additional_fields: Dict = {}
|
||||
else:
|
||||
self._additional_fields = dict(additional_fields)
|
||||
|
||||
def error_dict(self) -> "JsonDict":
|
||||
return cs_error(self.msg, self.errcode, **self._additional_fields)
|
||||
super().__init__(code, msg, errcode, additional_fields)
|
||||
|
||||
|
||||
class ConsentNotGivenError(SynapseError):
|
||||
|
|
|
@ -21,6 +21,7 @@ from typing import (
|
|||
Awaitable,
|
||||
Callable,
|
||||
Collection,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
|
@ -41,13 +42,17 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CHECK_EVENT_FOR_SPAM_CALLBACK = Callable[
|
||||
["synapse.events.EventBase"],
|
||||
Awaitable[
|
||||
Union[
|
||||
Allow,
|
||||
Codes,
|
||||
# Highly experimental, not officially part of the spamchecker API, may
|
||||
# disappear without warning depending on the results of ongoing
|
||||
# experiments.
|
||||
# Use this to return additional information as part of an error.
|
||||
Tuple[Codes, Dict],
|
||||
# Deprecated
|
||||
bool,
|
||||
# Deprecated
|
||||
|
@ -270,7 +275,7 @@ class SpamChecker:
|
|||
|
||||
async def check_event_for_spam(
|
||||
self, event: "synapse.events.EventBase"
|
||||
) -> Union[Decision, str]:
|
||||
) -> Union[Decision, Tuple[Codes, Dict], str]:
|
||||
"""Checks if a given event is considered "spammy" by this server.
|
||||
|
||||
If the server considers an event spammy, then it will be rejected if
|
||||
|
@ -293,9 +298,9 @@ class SpamChecker:
|
|||
with Measure(
|
||||
self.clock, "{}.{}".format(callback.__module__, callback.__qualname__)
|
||||
):
|
||||
res: Union[Decision, str, bool] = await delay_cancellation(
|
||||
callback(event)
|
||||
)
|
||||
res: Union[
|
||||
Decision, Tuple[Codes, Dict], str, bool
|
||||
] = await delay_cancellation(callback(event))
|
||||
if res is False or res is Allow.ALLOW:
|
||||
# This spam-checker accepts the event.
|
||||
# Other spam-checkers may reject it, though.
|
||||
|
@ -305,8 +310,9 @@ class SpamChecker:
|
|||
# return value `True`
|
||||
return Codes.FORBIDDEN
|
||||
else:
|
||||
# This spam-checker rejects the event either with a `str`
|
||||
# or with a `Codes`. In either case, we stop here.
|
||||
# This spam-checker rejects the event either with a `str`,
|
||||
# with a `Codes` or with a `Tuple[Codes, Dict]`. In either
|
||||
# case, we stop here.
|
||||
return res
|
||||
|
||||
# No spam-checker has rejected the event, let it pass.
|
||||
|
|
|
@ -895,6 +895,21 @@ class EventCreationHandler:
|
|||
|
||||
spam_check = await self.spam_checker.check_event_for_spam(event)
|
||||
if spam_check is not synapse.spam_checker_api.Allow.ALLOW:
|
||||
if isinstance(spam_check, tuple):
|
||||
try:
|
||||
[code, dict] = spam_check
|
||||
raise SynapseError(
|
||||
403,
|
||||
"This message had been rejected as probable spam",
|
||||
code,
|
||||
dict,
|
||||
)
|
||||
except ValueError:
|
||||
logger.error(
|
||||
"Spam-check module returned invalid error value. Expecting [code, dict], got %s",
|
||||
spam_check,
|
||||
)
|
||||
spam_check = Codes.FORBIDDEN
|
||||
raise SynapseError(
|
||||
403, "This message had been rejected as probable spam", spam_check
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue