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

Ratelimit invites by room and target user #9258

Merged
merged 9 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,8 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# "remote" for when users are trying to join rooms not on the server (which
# can be more expensive)
# - one for ratelimiting how often a user or IP can attempt to validate a 3PID.
# - two for ratelimiting how often invites can be sent in a room or to a
# specific user.
#
# The defaults are as shown below.
#
Expand Down Expand Up @@ -862,6 +864,14 @@ log_config: "CONFDIR/SERVERNAME.log.config"
#rc_3pid_validation:
# per_second: 0.003
# burst_count: 5
#
#rc_invites:
# per_room:
# per_second: 0.03
# burst_count: 10
# per_user:
# per_second: 0.03
# burst_count: 5

# Ratelimiting settings for incoming federation
#
Expand Down
19 changes: 19 additions & 0 deletions synapse/config/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def read_config(self, config, **kwargs):
defaults={"per_second": 0.003, "burst_count": 5},
)

self.rc_invites_per_room = RateLimitConfig(
config.get("rc_invites", {}).get("per_room", {}),
defaults={"per_second": 0.003, "burst_count": 10},
)
self.rc_invites_per_user = RateLimitConfig(
config.get("rc_invites", {}).get("per_user", {}),
defaults={"per_second": 0.003, "burst_count": 5},
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
)
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

def generate_config_section(self, **kwargs):
return """\
## Ratelimiting ##
Expand Down Expand Up @@ -137,6 +146,8 @@ def generate_config_section(self, **kwargs):
# "remote" for when users are trying to join rooms not on the server (which
# can be more expensive)
# - one for ratelimiting how often a user or IP can attempt to validate a 3PID.
# - two for ratelimiting how often invites can be sent in a room or to a
# specific user.
#
# The defaults are as shown below.
#
Expand Down Expand Up @@ -174,6 +185,14 @@ def generate_config_section(self, **kwargs):
#rc_3pid_validation:
# per_second: 0.003
# burst_count: 5
#
#rc_invites:
# per_room:
# per_second: 0.03
# burst_count: 10
# per_user:
# per_second: 0.03
# burst_count: 5

# Ratelimiting settings for incoming federation
#
Expand Down
4 changes: 4 additions & 0 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,10 @@ async def on_invite_request(
if event.state_key == self._server_notices_mxid:
raise SynapseError(HTTPStatus.FORBIDDEN, "Cannot invite this user")

# We retrieve the room member handler here as to not cause a cyclic dependency
member_handler = self.hs.get_room_member_handler()
member_handler.ratelimit_invite(event.room_id, event.state_key)

# keep a record of the room version, if we don't yet know it.
# (this may get overwritten if we later get a different room version in a
# join dance).
Expand Down
24 changes: 22 additions & 2 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ def __init__(self, hs: "HomeServer"):
burst_count=hs.config.ratelimiting.rc_joins_remote.burst_count,
)

self._invites_per_room_limiter = Ratelimiter(
clock=self.clock,
rate_hz=hs.config.ratelimiting.rc_invites_per_room.per_second,
burst_count=hs.config.ratelimiting.rc_invites_per_room.burst_count,
)
self._invites_per_user_limiter = Ratelimiter(
clock=self.clock,
rate_hz=hs.config.ratelimiting.rc_invites_per_user.per_second,
burst_count=hs.config.ratelimiting.rc_invites_per_user.burst_count,
)

# This is only used to get at ratelimit function, and
# maybe_kick_guest_users. It's fine there are multiple of these as
# it doesn't store state.
Expand Down Expand Up @@ -144,6 +155,12 @@ async def _user_left_room(self, target: UserID, room_id: str) -> None:
"""
raise NotImplementedError()

def ratelimit_invite(self, room_id: str, invitee_user_id: str):
"""Ratelimit invites py room and by target user.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
"""
self._invites_per_room_limiter.ratelimit(room_id)
self._invites_per_user_limiter.ratelimit(invitee_user_id)

async def _local_membership_update(
self,
requester: Requester,
Expand Down Expand Up @@ -387,8 +404,11 @@ async def update_membership_locked(
raise SynapseError(403, "This room has been blocked on this server")

if effective_membership_state == Membership.INVITE:
target_id = target.to_string()
self.ratelimit_invite(room_id, target_id)
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

# block any attempts to invite the server notices mxid
if target.to_string() == self._server_notices_mxid:
if target_id == self._server_notices_mxid:
raise SynapseError(HTTPStatus.FORBIDDEN, "Cannot invite this user")

block_invite = False
Expand All @@ -412,7 +432,7 @@ async def update_membership_locked(
block_invite = True

if not await self.spam_checker.user_may_invite(
requester.user.to_string(), target.to_string(), room_id
requester.user.to_string(), target_id, room_id
):
logger.info("Blocking invite due to spam checker")
block_invite = True
Expand Down