Allow use of different ratelimits for admin redactions.

This is useful to allow room admins to quickly deal with a large number
of abusive messages.
This commit is contained in:
Erik Johnston 2019-09-11 10:46:38 +01:00
parent 5e9b05d7da
commit 54ce81c86d
4 changed files with 55 additions and 11 deletions

View File

@ -80,6 +80,12 @@ class RatelimitConfig(Config):
"federation_rr_transactions_per_room_per_second", 50 "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): def generate_config_section(self, **kwargs):
return """\ return """\
## Ratelimiting ## ## Ratelimiting ##
@ -102,6 +108,9 @@ class RatelimitConfig(Config):
# - one for login that ratelimits login requests based on the account the # - 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 # client is attempting to log into, based on the amount of failed login
# attempts for this account. # 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 quickly deal with abuse quickly.
# #
# The defaults are as shown below. # The defaults are as shown below.
# #
@ -123,6 +132,10 @@ class RatelimitConfig(Config):
# failed_attempts: # failed_attempts:
# per_second: 0.17 # per_second: 0.17
# burst_count: 3 # burst_count: 3
#
#rc_admin_redaction:
# per_second: 1
# burst_count: 50
# Ratelimiting settings for incoming federation # Ratelimiting settings for incoming federation

View File

@ -45,6 +45,7 @@ class BaseHandler(object):
self.state_handler = hs.get_state_handler() self.state_handler = hs.get_state_handler()
self.distributor = hs.get_distributor() self.distributor = hs.get_distributor()
self.ratelimiter = hs.get_ratelimiter() self.ratelimiter = hs.get_ratelimiter()
self.admin_redaction_ratelimiter = hs.get_admin_redaction_ratelimiter()
self.clock = hs.get_clock() self.clock = hs.get_clock()
self.hs = hs self.hs = hs
@ -53,7 +54,7 @@ class BaseHandler(object):
self.event_builder_factory = hs.get_event_builder_factory() self.event_builder_factory = hs.get_event_builder_factory()
@defer.inlineCallbacks @defer.inlineCallbacks
def ratelimit(self, requester, update=True): def ratelimit(self, requester, update=True, is_admin_redaction=False):
"""Ratelimits requests. """Ratelimits requests.
Args: Args:
@ -62,6 +63,9 @@ class BaseHandler(object):
Set to False when doing multiple checks for one request (e.g. 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 to check up front if we would reject the request), and set to
True for the last call for a given request. 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: Raises:
LimitExceededError if the request should be ratelimited LimitExceededError if the request should be ratelimited
@ -90,16 +94,33 @@ class BaseHandler(object):
messages_per_second = override.messages_per_second messages_per_second = override.messages_per_second
burst_count = override.burst_count burst_count = override.burst_count
else: else:
messages_per_second = self.hs.config.rc_message.per_second # We default to different values if this is an admin redaction and
burst_count = self.hs.config.rc_message.burst_count # 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
allowed, time_allowed = self.ratelimiter.can_do_action( if is_admin_redaction and self.hs.config.rc_admin_redaction:
user_id, # If we have separate config for admin redactions we use a separate
time_now, # ratelimiter.
rate_hz=messages_per_second, allowed, time_allowed = self.admin_redaction_ratelimiter.can_do_action(
burst_count=burst_count, user_id,
update=update, 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: if not allowed:
raise LimitExceededError( raise LimitExceededError(
retry_after_ms=int(1000 * (time_allowed - time_now)) retry_after_ms=int(1000 * (time_allowed - time_now))

View File

@ -729,7 +729,13 @@ class EventCreationHandler(object):
assert not self.config.worker_app assert not self.config.worker_app
if ratelimit: if ratelimit:
yield self.base_handler.ratelimit(requester) is_admin_redaction = (
event.type == EventTypes.Redaction
and event.sender != requester.user.to_string()
)
yield self.base_handler.ratelimit(
requester, is_admin_redaction=is_admin_redaction
)
yield self.base_handler.maybe_kick_guest_users(event, context) yield self.base_handler.maybe_kick_guest_users(event, context)

View File

@ -221,6 +221,7 @@ class HomeServer(object):
self.clock = Clock(reactor) self.clock = Clock(reactor)
self.distributor = Distributor() self.distributor = Distributor()
self.ratelimiter = Ratelimiter() self.ratelimiter = Ratelimiter()
self.admin_redaction_ratelimiter = Ratelimiter()
self.registration_ratelimiter = Ratelimiter() self.registration_ratelimiter = Ratelimiter()
self.datastore = None self.datastore = None
@ -279,6 +280,9 @@ class HomeServer(object):
def get_registration_ratelimiter(self): def get_registration_ratelimiter(self):
return self.registration_ratelimiter return self.registration_ratelimiter
def get_admin_redaction_ratelimiter(self):
return self.admin_redaction_ratelimiter
def build_federation_client(self): def build_federation_client(self):
return FederationClient(self) return FederationClient(self)