diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py
index 82998ca490..d2e06c754e 100644
--- a/synapse/events/spamcheck.py
+++ b/synapse/events/spamcheck.py
@@ -31,7 +31,7 @@ from typing import (
from synapse.api.errors import Codes
from synapse.rest.media.v1._base import FileInfo
from synapse.rest.media.v1.media_storage import ReadableFileWrapper
-from synapse.spam_checker_api import Allow, Decision, RegistrationBehaviour
+from synapse.spam_checker_api import RegistrationBehaviour
from synapse.types import RoomAlias, UserProfile
from synapse.util.async_helpers import delay_cancellation, maybe_awaitable
from synapse.util.metrics import Measure
@@ -46,7 +46,7 @@ CHECK_EVENT_FOR_SPAM_CALLBACK = Callable[
["synapse.events.EventBase"],
Awaitable[
Union[
- Allow,
+ str,
Codes,
# Highly experimental, not officially part of the spamchecker API, may
# disappear without warning depending on the results of ongoing
@@ -55,8 +55,6 @@ CHECK_EVENT_FOR_SPAM_CALLBACK = Callable[
Tuple[Codes, Dict],
# Deprecated
bool,
- # Deprecated
- str,
]
],
]
@@ -183,6 +181,8 @@ def load_legacy_spam_checkers(hs: "synapse.server.HomeServer") -> None:
class SpamChecker:
+ NOT_SPAM = "NOT_SPAM"
+
def __init__(self, hs: "synapse.server.HomeServer") -> None:
self.hs = hs
self.clock = hs.get_clock()
@@ -275,7 +275,7 @@ class SpamChecker:
async def check_event_for_spam(
self, event: "synapse.events.EventBase"
- ) -> Union[Decision, Tuple[Codes, Dict], str]:
+ ) -> Union[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
@@ -286,22 +286,20 @@ class SpamChecker:
event: the event to be checked
Returns:
- - on `ALLOW`, the event is considered good (non-spammy) and should
- be let through. Other spamcheck filters may still reject it.
- - on `Code`, the event is considered spammy and is rejected with a specific
+ - `NOT_SPAM` if the event is considered good (non-spammy) and should be let
+ through. Other spamcheck filters may still reject it.
+ - A `Code` if the event is considered spammy and is rejected with a specific
error message/code.
- - on `str`, the event is considered spammy and the string is used as error
- message. This usage is generally discouraged as it doesn't support
- internationalization.
+ - A string that isn't `NOT_SPAM` if the event is considered spammy and the
+ string should be used as the client-facing error message. This usage is
+ generally discouraged as it doesn't support internationalization.
"""
for callback in self._check_event_for_spam_callbacks:
with Measure(
self.clock, "{}.{}".format(callback.__module__, callback.__qualname__)
):
- res: Union[
- Decision, Tuple[Codes, Dict], str, bool
- ] = await delay_cancellation(callback(event))
- if res is False or res is Allow.ALLOW:
+ res = await delay_cancellation(callback(event))
+ if res is False or res == self.NOT_SPAM:
# This spam-checker accepts the event.
# Other spam-checkers may reject it, though.
continue
@@ -309,14 +307,23 @@ class SpamChecker:
# This spam-checker rejects the event with deprecated
# return value `True`
return Codes.FORBIDDEN
+ elif not isinstance(res, str):
+ # mypy complains that we can't reach this code because of the
+ # return type in CHECK_EVENT_FOR_SPAM_CALLBACK, but we don't know
+ # for sure that the module actually returns it.
+ logger.warning(
+ "Module returned invalid value, rejecting message as spam"
+ )
+ res = "This message has been rejected as probable spam"
else:
- # 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
+ # The module rejected the event either with a `Codes`
+ # or some other `str`. In either case, we stop here.
+ pass
+
+ return res
# No spam-checker has rejected the event, let it pass.
- return Allow.ALLOW
+ return self.NOT_SPAM
async def should_drop_federated_event(
self, event: "synapse.events.EventBase"
diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py
index 1e866b19d8..7bc54b9988 100644
--- a/synapse/federation/federation_base.py
+++ b/synapse/federation/federation_base.py
@@ -15,7 +15,6 @@
import logging
from typing import TYPE_CHECKING
-import synapse
from synapse.api.constants import MAX_DEPTH, EventContentFields, EventTypes, Membership
from synapse.api.errors import Codes, SynapseError
from synapse.api.room_versions import EventFormatVersions, RoomVersion
@@ -101,7 +100,7 @@ class FederationBase:
spam_check = await self.spam_checker.check_event_for_spam(pdu)
- if spam_check is not synapse.spam_checker_api.Allow.ALLOW:
+ if spam_check != self.spam_checker.NOT_SPAM:
logger.warning("Event contains spam, soft-failing %s", pdu.event_id)
# we redact (to save disk space) as well as soft-failing (to stop
# using the event in prev_events).
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index f377769071..cf7c2d1979 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -23,7 +23,6 @@ from canonicaljson import encode_canonical_json
from twisted.internet.interfaces import IDelayedCall
-import synapse
from synapse import event_auth
from synapse.api.constants import (
EventContentFields,
@@ -897,11 +896,11 @@ class EventCreationHandler:
event.sender,
)
- 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):
+ spam_check_result = await self.spam_checker.check_event_for_spam(event)
+ if spam_check_result != self.spam_checker.NOT_SPAM:
+ if isinstance(spam_check_result, tuple):
try:
- [code, dict] = spam_check
+ [code, dict] = spam_check_result
raise SynapseError(
403,
"This message had been rejected as probable spam",
@@ -911,11 +910,24 @@ class EventCreationHandler:
except ValueError:
logger.error(
"Spam-check module returned invalid error value. Expecting [code, dict], got %s",
- spam_check,
+ spam_check_result,
)
- spam_check = Codes.FORBIDDEN
+ spam_check_result = Codes.FORBIDDEN
+
+ if isinstance(spam_check_result, Codes):
+ raise SynapseError(
+ 403,
+ "This message has been rejected as probable spam",
+ spam_check_result,
+ )
+
+ # Backwards compatibility: if the return value is not an error code, it
+ # means the module returned an error message to be included in the
+ # SynapseError (which is now deprecated).
raise SynapseError(
- 403, "This message had been rejected as probable spam", spam_check
+ 403,
+ spam_check_result,
+ Codes.FORBIDDEN,
)
ev = await self.handle_new_client_event(
diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 6668f64c90..b7451fc870 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -35,7 +35,6 @@ from typing_extensions import ParamSpec
from twisted.internet import defer
from twisted.web.resource import Resource
-from synapse import spam_checker_api
from synapse.api.errors import SynapseError
from synapse.events import EventBase
from synapse.events.presence_router import (
@@ -55,6 +54,7 @@ from synapse.events.spamcheck import (
USER_MAY_JOIN_ROOM_CALLBACK,
USER_MAY_PUBLISH_ROOM_CALLBACK,
USER_MAY_SEND_3PID_INVITE_CALLBACK,
+ SpamChecker,
)
from synapse.events.third_party_rules import (
CHECK_CAN_DEACTIVATE_USER_CALLBACK,
@@ -140,9 +140,7 @@ are loaded into Synapse.
"""
PRESENCE_ALL_USERS = PresenceRouter.ALL_USERS
-
-ALLOW = spam_checker_api.Allow.ALLOW
-# Singleton value used to mark a message as permitted.
+NOT_SPAM = SpamChecker.NOT_SPAM
__all__ = [
"errors",
@@ -151,7 +149,7 @@ __all__ = [
"respond_with_html",
"run_in_background",
"cached",
- "Allow",
+ "NOT_SPAM",
"UserID",
"DatabasePool",
"LoggingTransaction",
diff --git a/synapse/spam_checker_api/__init__.py b/synapse/spam_checker_api/__init__.py
index 95132c80b7..75578270ac 100644
--- a/synapse/spam_checker_api/__init__.py
+++ b/synapse/spam_checker_api/__init__.py
@@ -12,9 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import Enum
-from typing import Union
-
-from synapse.api.errors import Codes
class RegistrationBehaviour(Enum):
@@ -25,25 +22,3 @@ class RegistrationBehaviour(Enum):
ALLOW = "allow"
SHADOW_BAN = "shadow_ban"
DENY = "deny"
-
-
-# We define the following singleton enum rather than a string to be able to
-# write `Union[Allow, ..., str]` in some of the callbacks for the spam-checker
-# API, where the `str` is required to maintain backwards compatibility with
-# previous versions of the API.
-class Allow(Enum):
- """
- Singleton to allow events to pass through in SpamChecker APIs.
- """
-
- ALLOW = "allow"
-
-
-Decision = Union[Allow, Codes]
-"""
-Union to define whether a request should be allowed or rejected.
-
-To accept a request, return `ALLOW`.
-
-To reject a request without any specific information, use `Codes.FORBIDDEN`.
-"""
|