invites: Limit invites per day as a function of current users.

This commit is contained in:
Alex Vandiver 2023-02-28 20:32:07 +00:00 committed by Tim Abbott
parent 16436a2f15
commit 43800b4c55
1 changed files with 59 additions and 0 deletions

View File

@ -1,4 +1,5 @@
import datetime import datetime
import logging
from typing import Any, Collection, Dict, List, Optional, Sequence, Set, Tuple, Union from typing import Any, Collection, Dict, List, Optional, Sequence, Set, Tuple, Union
from django.conf import settings from django.conf import settings
@ -7,6 +8,7 @@ from django.db import transaction
from django.db.models import Q, Sum from django.db.models import Q, Sum
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from zxcvbn import zxcvbn
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat
from analytics.models import RealmCount from analytics.models import RealmCount
@ -97,6 +99,63 @@ def check_invite_limit(realm: Realm, num_invitees: int) -> None:
daily_limit_reached=True, daily_limit_reached=True,
) )
if realm.plan_type == Realm.PLAN_TYPE_LIMITED and realm._max_invites is None:
# If they're a non-paid plan with default invitation limits,
# we further limit how many invitations can be sent in a day
# as a function of how many current users they have. The
# allowed ratio has some heuristics to lock down likely-spammy
# realms. This ratio likely only matters for the first
# handful of invites; if those users accept, then the realm is
# unlikely to hit these limits. If a real realm hits them,
# the resulting message suggests that they contact support if
# they have a real use case.
suspicion_score = 0
if zxcvbn(realm.string_id)["score"] == 4:
# Very high entropy realm names are suspicious
suspicion_score += 1
if not realm.description:
suspicion_score += 1
if realm.icon_source == Realm.ICON_FROM_GRAVATAR:
suspicion_score += 1
if realm.date_created >= timezone_now() - datetime.timedelta(hours=1):
suspicion_score += 1
current_user_count = len(
UserProfile.objects.filter(realm=realm, is_bot=False, is_active=True)
)
if current_user_count == 1:
suspicion_score += 1
estimated_sent = RealmCount.objects.filter(
realm=realm, property="messages_sent:message_type:day"
).aggregate(messages=Sum("value"))
if estimated_sent["messages"] == 0:
suspicion_score += 1
if suspicion_score == 6:
permitted_ratio = 2
elif suspicion_score >= 3:
permitted_ratio = 3
else:
permitted_ratio = 5
# For now, simply log the data; this will change to a raise of
# InvitationError once we've done some auditing.
logging.warning(
"%s (suspicion %d/6) inviting %d more, have %d recent, %d max, but only %d current users. Ratio %.1f, %d allowed",
realm.string_id,
suspicion_score,
num_invitees,
recent_invites,
realm.max_invites,
current_user_count,
(num_invitees + recent_invites) / current_user_count,
permitted_ratio,
)
default_max = settings.INVITES_DEFAULT_REALM_DAILY_MAX default_max = settings.INVITES_DEFAULT_REALM_DAILY_MAX
newrealm_age = datetime.timedelta(days=settings.INVITES_NEW_REALM_DAYS) newrealm_age = datetime.timedelta(days=settings.INVITES_NEW_REALM_DAYS)
if realm.date_created <= timezone_now() - newrealm_age: if realm.date_created <= timezone_now() - newrealm_age: