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

Allow use of different ratelimits for admin redactions. #6015

Merged
merged 7 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions changelog.d/6015.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add config option to increase ratelimits for room admins redacting messages.
7 changes: 7 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,9 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# - one for login that ratelimits login requests based on the account the
# client is attempting to log into, based on the amount of failed login
# attempts for this account.
# - one for ratelimiting redactions by room admins. If this is not explicitly
# set then it uses the same ratelimiting as per rc_message. This is useful
# to allow room admins to deal with abuse quickly.
#
# The defaults are as shown below.
#
Expand All @@ -539,6 +542,10 @@ log_config: "CONFDIR/SERVERNAME.log.config"
# failed_attempts:
# per_second: 0.17
# burst_count: 3
#
#rc_admin_redaction:
# per_second: 1
# burst_count: 50


# Ratelimiting settings for incoming federation
Expand Down
13 changes: 13 additions & 0 deletions synapse/config/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def read_config(self, config, **kwargs):
"federation_rr_transactions_per_room_per_second", 50
)

rc_admin_redaction = config.get("rc_admin_redaction")
if rc_admin_redaction:
self.rc_admin_redaction = RateLimitConfig(rc_admin_redaction)
else:
self.rc_admin_redaction = None

def generate_config_section(self, **kwargs):
return """\
## Ratelimiting ##
Expand All @@ -102,6 +108,9 @@ def generate_config_section(self, **kwargs):
# - one for login that ratelimits login requests based on the account the
# client is attempting to log into, based on the amount of failed login
# attempts for this account.
# - one for ratelimiting redactions by room admins. If this is not explicitly
# set then it uses the same ratelimiting as per rc_message. This is useful
# to allow room admins to deal with abuse quickly.
#
# The defaults are as shown below.
#
Expand All @@ -123,6 +132,10 @@ def generate_config_section(self, **kwargs):
# failed_attempts:
# per_second: 0.17
# burst_count: 3
#
#rc_admin_redaction:
# per_second: 1
# burst_count: 50


# Ratelimiting settings for incoming federation
Expand Down
43 changes: 32 additions & 11 deletions synapse/handlers/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, hs):
self.state_handler = hs.get_state_handler()
self.distributor = hs.get_distributor()
self.ratelimiter = hs.get_ratelimiter()
self.admin_redaction_ratelimiter = hs.get_admin_redaction_ratelimiter()
self.clock = hs.get_clock()
self.hs = hs

Expand All @@ -53,7 +54,7 @@ def __init__(self, hs):
self.event_builder_factory = hs.get_event_builder_factory()

@defer.inlineCallbacks
def ratelimit(self, requester, update=True):
def ratelimit(self, requester, update=True, is_admin_redaction=False):
"""Ratelimits requests.

Args:
Expand All @@ -62,6 +63,9 @@ def ratelimit(self, requester, update=True):
Set to False when doing multiple checks for one request (e.g.
to check up front if we would reject the request), and set to
True for the last call for a given request.
is_admin_redaction (bool): Whether this is a room admin/moderator
redacting an event. If so then we may apply different
ratelimits depending on config.

Raises:
LimitExceededError if the request should be ratelimited
Expand Down Expand Up @@ -90,16 +94,33 @@ def ratelimit(self, requester, update=True):
messages_per_second = override.messages_per_second
burst_count = override.burst_count
else:
messages_per_second = self.hs.config.rc_message.per_second
burst_count = self.hs.config.rc_message.burst_count

allowed, time_allowed = self.ratelimiter.can_do_action(
user_id,
time_now,
rate_hz=messages_per_second,
burst_count=burst_count,
update=update,
)
# We default to different values if this is an admin redaction and
# the config is set
if is_admin_redaction and self.hs.config.rc_admin_redaction:
messages_per_second = self.hs.config.rc_admin_redaction.per_second
burst_count = self.hs.config.rc_admin_redaction.burst_count
else:
messages_per_second = self.hs.config.rc_message.per_second
burst_count = self.hs.config.rc_message.burst_count

if is_admin_redaction and self.hs.config.rc_admin_redaction:
# If we have separate config for admin redactions we use a separate
# ratelimiter
allowed, time_allowed = self.admin_redaction_ratelimiter.can_do_action(
user_id,
time_now,
rate_hz=messages_per_second,
burst_count=burst_count,
update=update,
)
else:
allowed, time_allowed = self.ratelimiter.can_do_action(
user_id,
time_now,
rate_hz=messages_per_second,
burst_count=burst_count,
update=update,
)
if not allowed:
raise LimitExceededError(
retry_after_ms=int(1000 * (time_allowed - time_now))
Expand Down
22 changes: 21 additions & 1 deletion synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,27 @@ def persist_and_notify_client_event(
assert not self.config.worker_app

if ratelimit:
yield self.base_handler.ratelimit(requester)
# We check if this is a room admin redacting an event so that we
# can apply different ratelimiting. We do this by simply checking
# it's not a self-redaction (to avoid having to look up whether the
# user is actually admin or not).
is_admin_redaction = False
if event.type == EventTypes.Redaction:
original_event = yield self.store.get_event(
event.redacts,
check_redacted=False,
get_prev_content=False,
allow_rejected=False,
allow_none=True,
)

is_admin_redaction = (
original_event and event.sender != original_event.sender
)

yield self.base_handler.ratelimit(
requester, is_admin_redaction=is_admin_redaction
)

yield self.base_handler.maybe_kick_guest_users(event, context)

Expand Down
4 changes: 4 additions & 0 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def __init__(self, hostname, reactor=None, **kwargs):
self.clock = Clock(reactor)
self.distributor = Distributor()
self.ratelimiter = Ratelimiter()
self.admin_redaction_ratelimiter = Ratelimiter()
self.registration_ratelimiter = Ratelimiter()

self.datastore = None
Expand Down Expand Up @@ -279,6 +280,9 @@ def get_ratelimiter(self):
def get_registration_ratelimiter(self):
return self.registration_ratelimiter

def get_admin_redaction_ratelimiter(self):
return self.admin_redaction_ratelimiter

def build_federation_client(self):
return FederationClient(self)

Expand Down
25 changes: 25 additions & 0 deletions tests/rest/client/test_redactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class RedactionsTestCase(HomeserverTestCase):
sync.register_servlets,
]

def make_homeserver(self, reactor, clock):
config = self.default_config()

config["rc_message"] = {"per_second": 0.2, "burst_count": 10}
config["rc_admin_redaction"] = {"per_second": 1, "burst_count": 100}

return self.setup_test_homeserver(config=config)

def prepare(self, reactor, clock, hs):
# register a couple of users
self.mod_user_id = self.register_user("user1", "pass")
Expand Down Expand Up @@ -177,3 +185,20 @@ def test_redact_create_event(self):
self._redact_event(
self.other_access_token, self.room_id, create_event_id, expect_code=403
)

def test_redact_event_as_moderator_ratelimit(self):
"""Tests that the correct ratelimiting is applied to redactions
"""

message_ids = []
# as a regular user, send messages to redact
for _ in range(20):
b = self.helper.send(room_id=self.room_id, tok=self.other_access_token)
message_ids.append(b["event_id"])
self.reactor.advance(10) # To get around ratelimits

# as the moderator, send a bunch of redactions
for msg_id in message_ids:
# These should all succeed, even though this would be denied by
# the standard message ratelimiter
self._redact_event(self.mod_access_token, self.room_id, msg_id)