def check_invite_limit(realm: Realm, num_invitees: int) -> None: """Discourage using invitation emails as a vector for carrying spam.""" msg = _( "To protect users, Zulip limits the number of invitations you can send in one day. Because you have reached the limit, no invitations were sent." ) if not settings.OPEN_REALM_CREATION: return recent_invites = estimate_recent_invites([realm], days=1) if num_invitees + recent_invites > realm.max_invites: raise InvitationError( msg, [], sent_invitations=False, daily_limit_reached=True, ) default_max = settings.INVITES_DEFAULT_REALM_DAILY_MAX newrealm_age = datetime.timedelta(days=settings.INVITES_NEW_REALM_DAYS) if realm.date_created <= timezone_now() - newrealm_age: # If this isn't a "newly-created" realm, we're done. The # remaining code applies an aggregate limit across all # "new" realms, to address sudden bursts of spam realms. return if realm.max_invites > default_max: # If a user is on a realm where we've bumped up # max_invites, then we exempt them from invite limits. return 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, daily_limit_reached=True, )
def check_spare_licenses_available_for_inviting_new_users(realm: Realm, num_invites: int) -> None: try: check_spare_licenses_available_for_adding_new_users(realm, num_invites) except LicenseLimitError: if num_invites == 1: message = _("All Zulip licenses for this organization are currently in use.") else: message = _( "Your organization does not have enough unused Zulip licenses to invite {num_invites} users." ).format(num_invites=num_invites) raise InvitationError(message, [], sent_invitations=False, license_limit_reached=True)
def do_invite_users( user_profile: UserProfile, invitee_emails: Collection[str], streams: Collection[Stream], *, invite_expires_in_minutes: Optional[int], invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"], ) -> None: num_invites = len(invitee_emails) check_invite_limit(user_profile.realm, num_invites) if settings.BILLING_ENABLED: from corporate.lib.registration import check_spare_licenses_available_for_inviting_new_users check_spare_licenses_available_for_inviting_new_users( user_profile.realm, num_invites) realm = user_profile.realm if not realm.invite_required: # Inhibit joining an open realm to send spam invitations. min_age = datetime.timedelta(days=settings.INVITES_MIN_USER_AGE_DAYS) if user_profile.date_joined > timezone_now( ) - min_age and not user_profile.is_realm_admin: raise InvitationError( _("Your account is too new to send invites for this organization. " "Ask an organization admin, or a more experienced user."), [], sent_invitations=False, ) good_emails: Set[str] = set() errors: List[Tuple[str, str, bool]] = [] validate_email_allowed_in_realm = get_realm_email_validator( user_profile.realm) for email in invitee_emails: if email == "": continue email_error = validate_email_is_valid( email, validate_email_allowed_in_realm, ) if email_error: errors.append((email, email_error, False)) else: good_emails.add(email) """ good_emails are emails that look ok so far, but we still need to make sure they're not gonna conflict with existing users """ error_dict = get_existing_user_errors(user_profile.realm, good_emails) skipped: List[Tuple[str, str, bool]] = [] for email in error_dict: msg, deactivated = error_dict[email] skipped.append((email, msg, deactivated)) good_emails.remove(email) validated_emails = list(good_emails) if errors: raise InvitationError( _("Some emails did not validate, so we didn't send any invitations." ), errors + skipped, sent_invitations=False, ) if skipped and len(skipped) == len(invitee_emails): # All e-mails were skipped, so we didn't actually invite anyone. raise InvitationError(_("We weren't able to invite anyone."), skipped, sent_invitations=False) # We do this here rather than in the invite queue processor since this # is used for rate limiting invitations, rather than keeping track of # when exactly invitations were sent do_increment_logging_stat( user_profile.realm, COUNT_STATS["invites_sent::day"], None, timezone_now(), increment=len(validated_emails), ) # Now that we are past all the possible errors, we actually create # the PreregistrationUser objects and trigger the email invitations. for email in validated_emails: # The logged in user is the referrer. prereg_user = PreregistrationUser(email=email, referred_by=user_profile, invited_as=invite_as, realm=user_profile.realm) prereg_user.save() stream_ids = [stream.id for stream in streams] prereg_user.streams.set(stream_ids) event = { "prereg_id": prereg_user.id, "referrer_id": user_profile.id, "email_language": user_profile.realm.default_language, "invite_expires_in_minutes": invite_expires_in_minutes, } queue_json_publish("invites", event) if skipped: raise InvitationError( _("Some of those addresses are already using Zulip, " "so we didn't send them an invitation. We did send " "invitations to everyone else!"), skipped, sent_invitations=True, ) notify_invites_changed(user_profile.realm)