antispam: Add a sitewide ratelimit on invites by new realms.

This applies only on a server open for anyone to create a realm.
Moreover, if the server admins have granted any given realm a
max_invites greater than the default, that realm is exempt too.
This commit is contained in:
Greg Price 2017-12-06 20:59:40 -08:00
parent dc1eeef30a
commit 22071a44a7
2 changed files with 26 additions and 9 deletions

View File

@ -4119,10 +4119,10 @@ class InvitationError(JsonableError):
self.errors = errors # type: List[Tuple[Text, str]]
self.sent_invitations = sent_invitations # type: bool
def estimate_recent_invites(realm: Realm, *, days: int) -> int:
def estimate_recent_invites(realms: Iterable[Realm], *, days: int) -> int:
'''An upper bound on the number of invites sent in the last `days` days'''
recent_invites = RealmCount.objects.filter(
realm=realm,
realm__in=realms,
property='invites_sent::day',
end_time__gte=timezone_now() - datetime.timedelta(days=days)
).aggregate(Sum('value'))['value__sum']
@ -4131,15 +4131,27 @@ def estimate_recent_invites(realm: Realm, *, days: int) -> int:
return recent_invites
def check_invite_limit(user: UserProfile, num_invitees: int) -> None:
# Discourage using invitation emails as a vector for carrying spam
if settings.OPEN_REALM_CREATION:
recent_invites = estimate_recent_invites(user.realm, days=1)
if num_invitees + recent_invites > user.realm.max_invites:
raise InvitationError(
_("You do not have enough remaining invites. "
'''Discourage using invitation emails as a vector for carrying spam.'''
msg = _("You do not have enough remaining invites. "
"Please contact %s to have your limit raised. "
"No invitations were sent." % (settings.ZULIP_ADMINISTRATOR)),
[], sent_invitations=False)
"No invitations were sent.") % (settings.ZULIP_ADMINISTRATOR,)
if settings.OPEN_REALM_CREATION:
recent_invites = estimate_recent_invites([user.realm], days=1)
if num_invitees + recent_invites > user.realm.max_invites:
raise InvitationError(msg, [], sent_invitations=False)
default_max = settings.INVITES_DEFAULT_REALM_DAILY_MAX
newrealm_age = datetime.timedelta(days=settings.INVITES_NEW_REALM_DAYS)
if (user.realm.date_created > timezone_now() - newrealm_age
and user.realm.max_invites <= default_max):
new_realms = Realm.objects.filter(
date_created__gte=timezone_now() - newrealm_age,
_max_invites__lte=default_max,
).all()
for days, count in settings.INVITES_NEW_REALM_LIMIT_DAYS:
recent_invites = estimate_recent_invites(new_realms, days=days)
if num_invitees + recent_invites > count:
raise InvitationError(msg, [], sent_invitations=False)
def do_invite_users(user_profile: UserProfile,
invitee_emails: SizedTextIterable,

View File

@ -309,6 +309,11 @@ DEFAULT_SETTINGS.update({
# Default for a realm's `max_invites`; which applies per day,
# and only applies if OPEN_REALM_CREATION is true.
'INVITES_DEFAULT_REALM_DAILY_MAX': 100,
# Global rate-limit (list of pairs (days, max)) on invites from new realms.
# Only applies if OPEN_REALM_CREATION is true.
'INVITES_NEW_REALM_LIMIT_DAYS': [(1, 100)],
# Definition of a new realm for INVITES_NEW_REALM_LIMIT.
'INVITES_NEW_REALM_DAYS': 7,
# Controls for which links are published in portico footers/headers/etc.
'EMAIL_DELIVERER_DISABLED': False,