diff --git a/changelog.d/19292.misc b/changelog.d/19292.misc new file mode 100644 index 00000000000..bce01072847 --- /dev/null +++ b/changelog.d/19292.misc @@ -0,0 +1 @@ +Add a config to be able to rate limit profile lookups by clients. diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index 7509e4d715e..5ed47d8e89b 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -2041,6 +2041,25 @@ rc_room_creation: burst_count: 5.0 ``` --- +### `rc_profile` + +*(object)* This option allows admins to ratelimit profile lookups by clients. + +_Added in Synapse 1.145.0._ + +This setting has the following sub-options: + +* `per_second` (number): Maximum number of requests a client can send per second. + +* `burst_count` (number): Maximum number of requests a client can send before being throttled. + +Default configuration: +```yaml +rc_profile: + per_second: 1.0 + burst_count: 500.0 +``` +--- ### `federation_rr_transactions_per_room_per_second` *(integer)* Sets outgoing federation transaction frequency for sending read-receipts, per-room. diff --git a/schema/synapse-config.schema.yaml b/schema/synapse-config.schema.yaml index bf9346995da..65640e4ed24 100644 --- a/schema/synapse-config.schema.yaml +++ b/schema/synapse-config.schema.yaml @@ -2274,6 +2274,16 @@ properties: examples: - per_second: 1.0 burst_count: 5.0 + rc_profile: + $ref: "#/$defs/rc" + description: >- + This option allows admins to ratelimit profile lookups by clients. + + + _Added in Synapse 1.145.0._ + default: + per_second: 1.0 + burst_count: 500.0 federation_rr_transactions_per_room_per_second: type: integer description: >- diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py index 78d9d61d3c6..d39fdea9bce 100644 --- a/synapse/config/ratelimiting.py +++ b/synapse/config/ratelimiting.py @@ -252,3 +252,9 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None: "rc_reports", defaults={"per_second": 1, "burst_count": 5}, ) + + self.rc_profile = RatelimitSettings.parse( + config, + "rc_profile", + defaults={"per_second": 1, "burst_count": 500}, + ) diff --git a/synapse/rest/client/profile.py b/synapse/rest/client/profile.py index 7f3128cb617..6797f114515 100644 --- a/synapse/rest/client/profile.py +++ b/synapse/rest/client/profile.py @@ -27,6 +27,7 @@ from synapse.api.constants import ProfileFields from synapse.api.errors import Codes, SynapseError +from synapse.api.ratelimiting import Ratelimiter from synapse.handlers.profile import MAX_CUSTOM_FIELD_LEN from synapse.http.server import HttpServer from synapse.http.servlet import ( @@ -67,22 +68,26 @@ def __init__(self, hs: "HomeServer"): self.profile_handler = hs.get_profile_handler() self.auth = hs.get_auth() + self._per_user_limiter = Ratelimiter( + store=hs.get_datastores().main, + clock=hs.get_clock(), + cfg=hs.config.ratelimiting.rc_profile, + ) + async def on_GET( self, request: SynapseRequest, user_id: str ) -> tuple[int, JsonDict]: - requester_user = None - - if self.hs.config.server.require_auth_for_profile_requests: - requester = await self.auth.get_user_by_req(request) - requester_user = requester.user - if not UserID.is_valid(user_id): raise SynapseError( HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM ) user = UserID.from_string(user_id) - await self.profile_handler.check_profile_query_allowed(user, requester_user) + + requester = await self.auth.get_user_by_req(request) + await self._per_user_limiter.ratelimit(requester) + if self.hs.config.server.require_auth_for_profile_requests: + await self.profile_handler.check_profile_query_allowed(user, requester.user) ret = await self.profile_handler.get_profile(user_id) @@ -116,15 +121,15 @@ def __init__(self, hs: "HomeServer"): ) ) + self._per_user_limiter = Ratelimiter( + store=hs.get_datastores().main, + clock=hs.get_clock(), + cfg=hs.config.ratelimiting.rc_profile, + ) + async def on_GET( self, request: SynapseRequest, user_id: str, field_name: str ) -> tuple[int, JsonDict]: - requester_user = None - - if self.hs.config.server.require_auth_for_profile_requests: - requester = await self.auth.get_user_by_req(request) - requester_user = requester.user - if not UserID.is_valid(user_id): raise SynapseError( HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM @@ -143,7 +148,11 @@ async def on_GET( ) user = UserID.from_string(user_id) - await self.profile_handler.check_profile_query_allowed(user, requester_user) + + requester = await self.auth.get_user_by_req(request) + await self._per_user_limiter.ratelimit(requester) + if self.hs.config.server.require_auth_for_profile_requests: + await self.profile_handler.check_profile_query_allowed(user, requester.user) if field_name == ProfileFields.DISPLAYNAME: field_value: JsonValue = await self.profile_handler.get_displayname(user)