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

Commit f14428b

Browse files
authored
Allow spam-checker modules to be provide async methods. (#8890)
Spam checker modules can now provide async methods. This is implemented in a backwards-compatible manner.
1 parent 5d34f40 commit f14428b

19 files changed

+98
-73
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 methods as `async`.

docs/spam_checker.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ well as some specific methods:
2222
* `user_may_create_room`
2323
* `user_may_create_room_alias`
2424
* `user_may_publish_room`
25+
* `check_username_for_spam`
26+
* `check_registration_for_spam`
2527

2628
The details of the each of these methods (as well as their inputs and outputs)
2729
are documented in the `synapse.events.spamcheck.SpamChecker` class.
@@ -32,28 +34,33 @@ call back into the homeserver internals.
3234
### Example
3335

3436
```python
37+
from synapse.spam_checker_api import RegistrationBehaviour
38+
3539
class ExampleSpamChecker:
3640
def __init__(self, config, api):
3741
self.config = config
3842
self.api = api
3943

40-
def check_event_for_spam(self, foo):
44+
async def check_event_for_spam(self, foo):
4145
return False # allow all events
4246

43-
def user_may_invite(self, inviter_userid, invitee_userid, room_id):
47+
async def user_may_invite(self, inviter_userid, invitee_userid, room_id):
4448
return True # allow all invites
4549

46-
def user_may_create_room(self, userid):
50+
async def user_may_create_room(self, userid):
4751
return True # allow all room creations
4852

49-
def user_may_create_room_alias(self, userid, room_alias):
53+
async def user_may_create_room_alias(self, userid, room_alias):
5054
return True # allow all room aliases
5155

52-
def user_may_publish_room(self, userid, room_id):
56+
async def user_may_publish_room(self, userid, room_id):
5357
return True # allow publishing of all rooms
5458

55-
def check_username_for_spam(self, user_profile):
59+
async def check_username_for_spam(self, user_profile):
5660
return False # allow all usernames
61+
62+
async def check_registration_for_spam(self, email_threepid, username, request_info):
63+
return RegistrationBehaviour.ALLOW # allow all registrations
5764
```
5865

5966
## Configuration

synapse/events/spamcheck.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
# limitations under the License.
1616

1717
import inspect
18-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
18+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
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,9 @@ 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(
44+
self, event: "synapse.events.EventBase"
45+
) -> Union[bool, str]:
4346
"""Checks if a given event is considered "spammy" by this server.
4447
4548
If the server considers an event spammy, then it will be rejected if
@@ -50,15 +53,16 @@ def check_event_for_spam(self, event: "synapse.events.EventBase") -> bool:
5053
event: the event to be checked
5154
5255
Returns:
53-
True if the event is spammy.
56+
True or a string if the event is spammy. If a string is returned it
57+
will be used as the error message returned to the user.
5458
"""
5559
for spam_checker in self.spam_checkers:
56-
if spam_checker.check_event_for_spam(event):
60+
if await maybe_awaitable(spam_checker.check_event_for_spam(event)):
5761
return True
5862

5963
return False
6064

61-
def user_may_invite(
65+
async def user_may_invite(
6266
self, inviter_userid: str, invitee_userid: str, room_id: str
6367
) -> bool:
6468
"""Checks if a given user may send an invite
@@ -75,14 +79,18 @@ def user_may_invite(
7579
"""
7680
for spam_checker in self.spam_checkers:
7781
if (
78-
spam_checker.user_may_invite(inviter_userid, invitee_userid, room_id)
82+
await maybe_awaitable(
83+
spam_checker.user_may_invite(
84+
inviter_userid, invitee_userid, room_id
85+
)
86+
)
7987
is False
8088
):
8189
return False
8290

8391
return True
8492

85-
def user_may_create_room(self, userid: str) -> bool:
93+
async def user_may_create_room(self, userid: str) -> bool:
8694
"""Checks if a given user may create a room
8795
8896
If this method returns false, the creation request will be rejected.
@@ -94,12 +102,15 @@ def user_may_create_room(self, userid: str) -> bool:
94102
True if the user may create a room, otherwise False
95103
"""
96104
for spam_checker in self.spam_checkers:
97-
if spam_checker.user_may_create_room(userid) is False:
105+
if (
106+
await maybe_awaitable(spam_checker.user_may_create_room(userid))
107+
is False
108+
):
98109
return False
99110

100111
return True
101112

102-
def user_may_create_room_alias(self, userid: str, room_alias: str) -> bool:
113+
async def user_may_create_room_alias(self, userid: str, room_alias: str) -> bool:
103114
"""Checks if a given user may create a room alias
104115
105116
If this method returns false, the association request will be rejected.
@@ -112,12 +123,17 @@ def user_may_create_room_alias(self, userid: str, room_alias: str) -> bool:
112123
True if the user may create a room alias, otherwise False
113124
"""
114125
for spam_checker in self.spam_checkers:
115-
if spam_checker.user_may_create_room_alias(userid, room_alias) is False:
126+
if (
127+
await maybe_awaitable(
128+
spam_checker.user_may_create_room_alias(userid, room_alias)
129+
)
130+
is False
131+
):
116132
return False
117133

118134
return True
119135

120-
def user_may_publish_room(self, userid: str, room_id: str) -> bool:
136+
async def user_may_publish_room(self, userid: str, room_id: str) -> bool:
121137
"""Checks if a given user may publish a room to the directory
122138
123139
If this method returns false, the publish request will be rejected.
@@ -130,12 +146,17 @@ def user_may_publish_room(self, userid: str, room_id: str) -> bool:
130146
True if the user may publish the room, otherwise False
131147
"""
132148
for spam_checker in self.spam_checkers:
133-
if spam_checker.user_may_publish_room(userid, room_id) is False:
149+
if (
150+
await maybe_awaitable(
151+
spam_checker.user_may_publish_room(userid, room_id)
152+
)
153+
is False
154+
):
134155
return False
135156

136157
return True
137158

138-
def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
159+
async def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
139160
"""Checks if a user ID or display name are considered "spammy" by this server.
140161
141162
If the server considers a username spammy, then it will not be included in
@@ -157,12 +178,12 @@ def check_username_for_spam(self, user_profile: Dict[str, str]) -> bool:
157178
if checker:
158179
# Make a copy of the user profile object to ensure the spam checker
159180
# cannot modify it.
160-
if checker(user_profile.copy()):
181+
if await maybe_awaitable(checker(user_profile.copy())):
161182
return True
162183

163184
return False
164185

165-
def check_registration_for_spam(
186+
async def check_registration_for_spam(
166187
self,
167188
email_threepid: Optional[dict],
168189
username: Optional[str],
@@ -185,7 +206,9 @@ def check_registration_for_spam(
185206
# spam checker
186207
checker = getattr(spam_checker, "check_registration_for_spam", None)
187208
if checker:
188-
behaviour = checker(email_threepid, username, request_info)
209+
behaviour = await maybe_awaitable(
210+
checker(email_threepid, username, request_info)
211+
)
189212
assert isinstance(behaviour, RegistrationBehaviour)
190213
if behaviour != RegistrationBehaviour.ALLOW:
191214
return behaviour

synapse/federation/federation_base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def _check_sigs_and_hashes(
7878

7979
ctx = current_context()
8080

81+
@defer.inlineCallbacks
8182
def callback(_, pdu: EventBase):
8283
with PreserveLoggingContext(ctx):
8384
if not check_event_content_hash(pdu):
@@ -105,7 +106,11 @@ def callback(_, pdu: EventBase):
105106
)
106107
return redacted_event
107108

108-
if self.spam_checker.check_event_for_spam(pdu):
109+
result = yield defer.ensureDeferred(
110+
self.spam_checker.check_event_for_spam(pdu)
111+
)
112+
113+
if result:
109114
logger.warning(
110115
"Event contains spam, redacting %s: %s",
111116
pdu.event_id,

synapse/handlers/auth.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
17-
import inspect
1817
import logging
1918
import time
2019
import unicodedata
@@ -59,6 +58,7 @@
5958
from synapse.module_api import ModuleApi
6059
from synapse.types import JsonDict, Requester, UserID
6160
from synapse.util import stringutils as stringutils
61+
from synapse.util.async_helpers import maybe_awaitable
6262
from synapse.util.msisdn import phone_number_to_msisdn
6363
from synapse.util.threepids import canonicalise_email
6464

@@ -1639,6 +1639,6 @@ async def on_logged_out(
16391639

16401640
# This might return an awaitable, if it does block the log out
16411641
# until it completes.
1642-
result = g(user_id=user_id, device_id=device_id, access_token=access_token,)
1643-
if inspect.isawaitable(result):
1644-
await result
1642+
await maybe_awaitable(
1643+
g(user_id=user_id, device_id=device_id, access_token=access_token,)
1644+
)

synapse/handlers/directory.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ async def create_association(
133133
403, "You must be in the room to create an alias for it"
134134
)
135135

136-
if not self.spam_checker.user_may_create_room_alias(user_id, room_alias):
136+
if not await self.spam_checker.user_may_create_room_alias(
137+
user_id, room_alias
138+
):
137139
raise AuthError(403, "This user is not permitted to create this alias")
138140

139141
if not self.config.is_alias_creation_allowed(
@@ -409,7 +411,7 @@ async def edit_published_room_list(
409411
"""
410412
user_id = requester.user.to_string()
411413

412-
if not self.spam_checker.user_may_publish_room(user_id, room_id):
414+
if not await self.spam_checker.user_may_publish_room(user_id, room_id):
413415
raise AuthError(
414416
403, "This user is not permitted to publish rooms to the room list"
415417
)

synapse/handlers/federation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,7 @@ async def on_invite_request(
15931593
if self.hs.config.block_non_admin_invites:
15941594
raise SynapseError(403, "This server does not accept room invites")
15951595

1596-
if not self.spam_checker.user_may_invite(
1596+
if not await self.spam_checker.user_may_invite(
15971597
event.sender, event.state_key, event.room_id
15981598
):
15991599
raise SynapseError(

synapse/handlers/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ 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 self.spam_checker.check_event_for_spam(event)
748748
if spam_error:
749749
if not isinstance(spam_error, str):
750750
spam_error = "Spam is not permitted here"

synapse/handlers/receipts.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from synapse.appservice import ApplicationService
1919
from synapse.handlers._base import BaseHandler
2020
from synapse.types import JsonDict, ReadReceipt, get_domain_from_id
21-
from synapse.util.async_helpers import maybe_awaitable
2221

2322
logger = logging.getLogger(__name__)
2423

@@ -98,10 +97,8 @@ async def _handle_new_receipts(self, receipts):
9897

9998
self.notifier.on_new_event("receipt_key", max_batch_id, rooms=affected_room_ids)
10099
# Note that the min here shouldn't be relied upon to be accurate.
101-
await maybe_awaitable(
102-
self.hs.get_pusherpool().on_new_receipts(
103-
min_batch_id, max_batch_id, affected_room_ids
104-
)
100+
await self.hs.get_pusherpool().on_new_receipts(
101+
min_batch_id, max_batch_id, affected_room_ids
105102
)
106103

107104
return True

synapse/handlers/register.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ async def register_user(
187187
"""
188188
self.check_registration_ratelimit(address)
189189

190-
result = self.spam_checker.check_registration_for_spam(
190+
result = await self.spam_checker.check_registration_for_spam(
191191
threepid, localpart, user_agent_ips or [],
192192
)
193193

0 commit comments

Comments
 (0)