Example #1
0
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,
            )
Example #2
0
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)
Example #3
0
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)