summary refs log tree commit diff
path: root/synapse/module_api
diff options
context:
space:
mode:
authorHugh Nimmo-Smith <hughns@users.noreply.github.com>2025-06-04 13:09:11 +0100
committerGitHub <noreply@github.com>2025-06-04 12:09:11 +0000
commit9b2bc75ed49ea52008dc787a09337ca4a843d8e7 (patch)
tree2035b651bd0452e2a4f8563b16565a285b0f6759 /synapse/module_api
parentAdd user_may_send_state_event callback to spam checker module API (#18455) (diff)
downloadsynapse-9b2bc75ed49ea52008dc787a09337ca4a843d8e7.tar.xz
Add ratelimit callbacks to module API to allow dynamic ratelimiting (#18458)
Diffstat (limited to 'synapse/module_api')
-rw-r--r--synapse/module_api/__init__.py19
-rw-r--r--synapse/module_api/callbacks/__init__.py4
-rw-r--r--synapse/module_api/callbacks/ratelimit_callbacks.py62
3 files changed, 84 insertions, 1 deletions
diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py

index 3148957229..e36503a526 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py
@@ -94,6 +94,9 @@ from synapse.module_api.callbacks.media_repository_callbacks import ( GET_MEDIA_CONFIG_FOR_USER_CALLBACK, IS_USER_ALLOWED_TO_UPLOAD_MEDIA_OF_SIZE_CALLBACK, ) +from synapse.module_api.callbacks.ratelimit_callbacks import ( + GET_RATELIMIT_OVERRIDE_FOR_USER_CALLBACK, +) from synapse.module_api.callbacks.spamchecker_callbacks import ( CHECK_EVENT_FOR_SPAM_CALLBACK, CHECK_LOGIN_FOR_SPAM_CALLBACK, @@ -367,6 +370,20 @@ class ModuleApi: on_legacy_admin_request=on_legacy_admin_request, ) + def register_ratelimit_callbacks( + self, + *, + get_ratelimit_override_for_user: Optional[ + GET_RATELIMIT_OVERRIDE_FOR_USER_CALLBACK + ] = None, + ) -> None: + """Registers callbacks for ratelimit capabilities. + Added in Synapse v1.132.0. + """ + return self._callbacks.ratelimit.register_callbacks( + get_ratelimit_override_for_user=get_ratelimit_override_for_user, + ) + def register_media_repository_callbacks( self, *, @@ -376,7 +393,7 @@ class ModuleApi: ] = None, ) -> None: """Registers callbacks for media repository capabilities. - Added in Synapse v1.x.x. + Added in Synapse v1.132.0. """ return self._callbacks.media_repository.register_callbacks( get_media_config_for_user=get_media_config_for_user, diff --git a/synapse/module_api/callbacks/__init__.py b/synapse/module_api/callbacks/__init__.py
index a36c0fc7c6..16ef7a4b47 100644 --- a/synapse/module_api/callbacks/__init__.py +++ b/synapse/module_api/callbacks/__init__.py
@@ -30,6 +30,9 @@ from synapse.module_api.callbacks.account_validity_callbacks import ( from synapse.module_api.callbacks.media_repository_callbacks import ( MediaRepositoryModuleApiCallbacks, ) +from synapse.module_api.callbacks.ratelimit_callbacks import ( + RatelimitModuleApiCallbacks, +) from synapse.module_api.callbacks.spamchecker_callbacks import ( SpamCheckerModuleApiCallbacks, ) @@ -42,5 +45,6 @@ class ModuleApiCallbacks: def __init__(self, hs: "HomeServer") -> None: self.account_validity = AccountValidityModuleApiCallbacks() self.media_repository = MediaRepositoryModuleApiCallbacks(hs) + self.ratelimit = RatelimitModuleApiCallbacks(hs) self.spam_checker = SpamCheckerModuleApiCallbacks(hs) self.third_party_event_rules = ThirdPartyEventRulesModuleApiCallbacks(hs) diff --git a/synapse/module_api/callbacks/ratelimit_callbacks.py b/synapse/module_api/callbacks/ratelimit_callbacks.py new file mode 100644
index 0000000000..ced721b407 --- /dev/null +++ b/synapse/module_api/callbacks/ratelimit_callbacks.py
@@ -0,0 +1,62 @@ +# +# This file is licensed under the Affero General Public License (AGPL) version 3. +# +# Copyright (C) 2025 New Vector, Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# See the GNU Affero General Public License for more details: +# <https://www.gnu.org/licenses/agpl-3.0.html>. +# + +import logging +from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional + +from synapse.storage.databases.main.room import RatelimitOverride +from synapse.util.async_helpers import delay_cancellation +from synapse.util.metrics import Measure + +if TYPE_CHECKING: + from synapse.server import HomeServer + +logger = logging.getLogger(__name__) + +GET_RATELIMIT_OVERRIDE_FOR_USER_CALLBACK = Callable[ + [str, str], Awaitable[Optional[RatelimitOverride]] +] + + +class RatelimitModuleApiCallbacks: + def __init__(self, hs: "HomeServer") -> None: + self.clock = hs.get_clock() + self._get_ratelimit_override_for_user_callbacks: List[ + GET_RATELIMIT_OVERRIDE_FOR_USER_CALLBACK + ] = [] + + def register_callbacks( + self, + get_ratelimit_override_for_user: Optional[ + GET_RATELIMIT_OVERRIDE_FOR_USER_CALLBACK + ] = None, + ) -> None: + """Register callbacks from module for each hook.""" + if get_ratelimit_override_for_user is not None: + self._get_ratelimit_override_for_user_callbacks.append( + get_ratelimit_override_for_user + ) + + async def get_ratelimit_override_for_user( + self, user_id: str, limiter_name: str + ) -> Optional[RatelimitOverride]: + for callback in self._get_ratelimit_override_for_user_callbacks: + with Measure(self.clock, f"{callback.__module__}.{callback.__qualname__}"): + res: Optional[RatelimitOverride] = await delay_cancellation( + callback(user_id, limiter_name) + ) + if res: + return res + + return None