Skip to content

Commit 8fd8773

Browse files
babolivierrichvdh
andauthored
Fix import in module_api module and docs on the new check_event_for_spam signature (matrix-org#12918)
Co-authored-by: Richard van der Hoff <[email protected]>
1 parent e409ab8 commit 8fd8773

File tree

8 files changed

+66
-78
lines changed

8 files changed

+66
-78
lines changed

changelog.d/12918.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug introduced in Synapse 1.60.0rc1 that would break some imports from `synapse.module_api`.

docs/modules/spam_checker_callbacks.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,28 @@ The available spam checker callbacks are:
1111
### `check_event_for_spam`
1212

1313
_First introduced in Synapse v1.37.0_
14-
_Signature extended to support Allow and Code in Synapse v1.60.0_
15-
_Boolean and string return value types deprecated in Synapse v1.60.0_
14+
15+
_Changed in Synapse v1.60.0: `synapse.module_api.NOT_SPAM` and `synapse.module_api.errors.Codes` can be returned by this callback. Returning a boolean or a string is now deprecated._
1616

1717
```python
18-
async def check_event_for_spam(event: "synapse.module_api.EventBase") -> Union["synapse.module_api.ALLOW", "synapse.module_api.error.Codes", str, bool]
18+
async def check_event_for_spam(event: "synapse.module_api.EventBase") -> Union["synapse.module_api.NOT_SPAM", "synapse.module_api.errors.Codes", str, bool]
1919
```
2020

21-
Called when receiving an event from a client or via federation. The callback must return either:
22-
- `synapse.module_api.ALLOW`, to allow the operation. Other callbacks
23-
may still decide to reject it.
24-
- `synapse.api.Codes` to reject the operation with an error code. In case
25-
of doubt, `synapse.api.error.Codes.FORBIDDEN` is a good error code.
26-
- (deprecated) a `str` to reject the operation and specify an error message. Note that clients
21+
Called when receiving an event from a client or via federation. The callback must return one of:
22+
- `synapse.module_api.NOT_SPAM`, to allow the operation. Other callbacks may still
23+
decide to reject it.
24+
- `synapse.module_api.errors.Codes` to reject the operation with an error code. In case
25+
of doubt, `synapse.module_api.errors.Codes.FORBIDDEN` is a good error code.
26+
- (deprecated) a non-`Codes` `str` to reject the operation and specify an error message. Note that clients
2727
typically will not localize the error message to the user's preferred locale.
28-
- (deprecated) on `False`, behave as `ALLOW`. Deprecated as confusing, as some
29-
callbacks in expect `True` to allow and others `True` to reject.
30-
- (deprecated) on `True`, behave as `synapse.api.error.Codes.FORBIDDEN`. Deprecated as confusing, as
31-
some callbacks in expect `True` to allow and others `True` to reject.
28+
- (deprecated) `False`, which is the same as returning `synapse.module_api.NOT_SPAM`.
29+
- (deprecated) `True`, which is the same as returning `synapse.module_api.errors.Codes.FORBIDDEN`.
3230

3331
If multiple modules implement this callback, they will be considered in order. If a
34-
callback returns `synapse.module_api.ALLOW`, Synapse falls through to the next one. The value of the
35-
first callback that does not return `synapse.module_api.ALLOW` will be used. If this happens, Synapse
36-
will not call any of the subsequent implementations of this callback.
32+
callback returns `synapse.module_api.NOT_SPAM`, Synapse falls through to the next one.
33+
The value of the first callback that does not return `synapse.module_api.NOT_SPAM` will
34+
be used. If this happens, Synapse will not call any of the subsequent implementations of
35+
this callback.
3736

3837
### `user_may_join_room`
3938

docs/upgrade.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,11 @@ has queries that can be used to check a database for this problem in advance.
177177
178178
</details>
179179
180-
## SpamChecker API's `check_event_for_spam` has a new signature.
180+
## New signature for the spam checker callback `check_event_for_spam`
181181
182182
The previous signature has been deprecated.
183183
184-
Whereas `check_event_for_spam` callbacks used to return `Union[str, bool]`, they should now return `Union["synapse.module_api.Allow", "synapse.module_api.errors.Codes"]`.
184+
Whereas `check_event_for_spam` callbacks used to return `Union[str, bool]`, they should now return `Union["synapse.module_api.NOT_SPAM", "synapse.module_api.errors.Codes"]`.
185185
186186
This is part of an ongoing refactoring of the SpamChecker API to make it less ambiguous and more powerful.
187187
@@ -204,8 +204,8 @@ async def check_event_for_spam(event):
204204
# Event is spam, mark it as forbidden (you may use some more precise error
205205
# code if it is useful).
206206
return synapse.module_api.errors.Codes.FORBIDDEN
207-
# Event is not spam, mark it as `ALLOW`.
208-
return synapse.module_api.ALLOW
207+
# Event is not spam, mark it as such.
208+
return synapse.module_api.NOT_SPAM
209209
```
210210
211211
# Upgrading to v1.59.0

synapse/events/spamcheck.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from synapse.api.errors import Codes
3131
from synapse.rest.media.v1._base import FileInfo
3232
from synapse.rest.media.v1.media_storage import ReadableFileWrapper
33-
from synapse.spam_checker_api import Allow, Decision, RegistrationBehaviour
33+
from synapse.spam_checker_api import RegistrationBehaviour
3434
from synapse.types import RoomAlias, UserProfile
3535
from synapse.util.async_helpers import delay_cancellation, maybe_awaitable
3636
from synapse.util.metrics import Measure
@@ -46,12 +46,9 @@
4646
["synapse.events.EventBase"],
4747
Awaitable[
4848
Union[
49-
Allow,
50-
Codes,
49+
str,
5150
# Deprecated
5251
bool,
53-
# Deprecated
54-
str,
5552
]
5653
],
5754
]
@@ -178,6 +175,8 @@ def run(*args: Any, **kwargs: Any) -> Awaitable:
178175

179176

180177
class SpamChecker:
178+
NOT_SPAM = "NOT_SPAM"
179+
181180
def __init__(self, hs: "synapse.server.HomeServer") -> None:
182181
self.hs = hs
183182
self.clock = hs.get_clock()
@@ -268,9 +267,7 @@ def register_callbacks(
268267
if check_media_file_for_spam is not None:
269268
self._check_media_file_for_spam_callbacks.append(check_media_file_for_spam)
270269

271-
async def check_event_for_spam(
272-
self, event: "synapse.events.EventBase"
273-
) -> Union[Decision, str]:
270+
async def check_event_for_spam(self, event: "synapse.events.EventBase") -> str:
274271
"""Checks if a given event is considered "spammy" by this server.
275272
276273
If the server considers an event spammy, then it will be rejected if
@@ -281,36 +278,44 @@ async def check_event_for_spam(
281278
event: the event to be checked
282279
283280
Returns:
284-
- on `ALLOW`, the event is considered good (non-spammy) and should
285-
be let through. Other spamcheck filters may still reject it.
286-
- on `Code`, the event is considered spammy and is rejected with a specific
281+
- `NOT_SPAM` if the event is considered good (non-spammy) and should be let
282+
through. Other spamcheck filters may still reject it.
283+
- A `Code` if the event is considered spammy and is rejected with a specific
287284
error message/code.
288-
- on `str`, the event is considered spammy and the string is used as error
289-
message. This usage is generally discouraged as it doesn't support
290-
internationalization.
285+
- A string that isn't `NOT_SPAM` if the event is considered spammy and the
286+
string should be used as the client-facing error message. This usage is
287+
generally discouraged as it doesn't support internationalization.
291288
"""
292289
for callback in self._check_event_for_spam_callbacks:
293290
with Measure(
294291
self.clock, "{}.{}".format(callback.__module__, callback.__qualname__)
295292
):
296-
res: Union[Decision, str, bool] = await delay_cancellation(
297-
callback(event)
298-
)
299-
if res is False or res is Allow.ALLOW:
293+
res = await delay_cancellation(callback(event))
294+
if res is False or res == self.NOT_SPAM:
300295
# This spam-checker accepts the event.
301296
# Other spam-checkers may reject it, though.
302297
continue
303298
elif res is True:
304299
# This spam-checker rejects the event with deprecated
305300
# return value `True`
306301
return Codes.FORBIDDEN
302+
elif not isinstance(res, str):
303+
# mypy complains that we can't reach this code because of the
304+
# return type in CHECK_EVENT_FOR_SPAM_CALLBACK, but we don't know
305+
# for sure that the module actually returns it.
306+
logger.warning( # type: ignore[unreachable]
307+
"Module returned invalid value, rejecting message as spam"
308+
)
309+
res = "This message has been rejected as probable spam"
307310
else:
308-
# This spam-checker rejects the event either with a `str`
309-
# or with a `Codes`. In either case, we stop here.
310-
return res
311+
# The module rejected the event either with a `Codes`
312+
# or some other `str`. In either case, we stop here.
313+
pass
314+
315+
return res
311316

312317
# No spam-checker has rejected the event, let it pass.
313-
return Allow.ALLOW
318+
return self.NOT_SPAM
314319

315320
async def should_drop_federated_event(
316321
self, event: "synapse.events.EventBase"

synapse/federation/federation_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import logging
1616
from typing import TYPE_CHECKING
1717

18-
import synapse
1918
from synapse.api.constants import MAX_DEPTH, EventContentFields, EventTypes, Membership
2019
from synapse.api.errors import Codes, SynapseError
2120
from synapse.api.room_versions import EventFormatVersions, RoomVersion
@@ -101,7 +100,7 @@ async def _check_sigs_and_hash(
101100

102101
spam_check = await self.spam_checker.check_event_for_spam(pdu)
103102

104-
if spam_check is not synapse.spam_checker_api.Allow.ALLOW:
103+
if spam_check != self.spam_checker.NOT_SPAM:
105104
logger.warning("Event contains spam, soft-failing %s", pdu.event_id)
106105
# we redact (to save disk space) as well as soft-failing (to stop
107106
# using the event in prev_events).

synapse/handlers/message.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
from twisted.internet.interfaces import IDelayedCall
2525

26-
import synapse
2726
from synapse import event_auth
2827
from synapse.api.constants import (
2928
EventContentFields,
@@ -886,10 +885,22 @@ async def create_and_send_nonmember_event(
886885
event.sender,
887886
)
888887

889-
spam_check = await self.spam_checker.check_event_for_spam(event)
890-
if spam_check is not synapse.spam_checker_api.Allow.ALLOW:
888+
spam_check_result = await self.spam_checker.check_event_for_spam(event)
889+
if spam_check_result != self.spam_checker.NOT_SPAM:
890+
if isinstance(spam_check_result, Codes):
891+
raise SynapseError(
892+
403,
893+
"This message has been rejected as probable spam",
894+
spam_check_result,
895+
)
896+
897+
# Backwards compatibility: if the return value is not an error code, it
898+
# means the module returned an error message to be included in the
899+
# SynapseError (which is now deprecated).
891900
raise SynapseError(
892-
403, "This message had been rejected as probable spam", spam_check
901+
403,
902+
spam_check_result,
903+
Codes.FORBIDDEN,
893904
)
894905

895906
ev = await self.handle_new_client_event(

synapse/module_api/__init__.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from twisted.internet import defer
3636
from twisted.web.resource import Resource
3737

38-
from synapse import spam_checker_api
3938
from synapse.api.errors import SynapseError
4039
from synapse.events import EventBase
4140
from synapse.events.presence_router import (
@@ -55,6 +54,7 @@
5554
USER_MAY_JOIN_ROOM_CALLBACK,
5655
USER_MAY_PUBLISH_ROOM_CALLBACK,
5756
USER_MAY_SEND_3PID_INVITE_CALLBACK,
57+
SpamChecker,
5858
)
5959
from synapse.events.third_party_rules import (
6060
CHECK_CAN_DEACTIVATE_USER_CALLBACK,
@@ -140,9 +140,7 @@
140140
"""
141141

142142
PRESENCE_ALL_USERS = PresenceRouter.ALL_USERS
143-
144-
ALLOW = spam_checker_api.Allow.ALLOW
145-
# Singleton value used to mark a message as permitted.
143+
NOT_SPAM = SpamChecker.NOT_SPAM
146144

147145
__all__ = [
148146
"errors",
@@ -151,7 +149,7 @@
151149
"respond_with_html",
152150
"run_in_background",
153151
"cached",
154-
"Allow",
152+
"NOT_SPAM",
155153
"UserID",
156154
"DatabasePool",
157155
"LoggingTransaction",

synapse/spam_checker_api/__init__.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from enum import Enum
15-
from typing import Union
16-
17-
from synapse.api.errors import Codes
1815

1916

2017
class RegistrationBehaviour(Enum):
@@ -25,25 +22,3 @@ class RegistrationBehaviour(Enum):
2522
ALLOW = "allow"
2623
SHADOW_BAN = "shadow_ban"
2724
DENY = "deny"
28-
29-
30-
# We define the following singleton enum rather than a string to be able to
31-
# write `Union[Allow, ..., str]` in some of the callbacks for the spam-checker
32-
# API, where the `str` is required to maintain backwards compatibility with
33-
# previous versions of the API.
34-
class Allow(Enum):
35-
"""
36-
Singleton to allow events to pass through in SpamChecker APIs.
37-
"""
38-
39-
ALLOW = "allow"
40-
41-
42-
Decision = Union[Allow, Codes]
43-
"""
44-
Union to define whether a request should be allowed or rejected.
45-
46-
To accept a request, return `ALLOW`.
47-
48-
To reject a request without any specific information, use `Codes.FORBIDDEN`.
49-
"""

0 commit comments

Comments
 (0)