diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index a3a6a1d118c..249b6fe1192 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -55,6 +55,7 @@ module Email def initialize(mail_string, opts = {}) raise EmptyEmailError if mail_string.blank? @staged_users = [] + @created_staged_users = [] @raw_email = mail_string COMMON_ENCODINGS.each do |encoding| @@ -92,7 +93,7 @@ module Email post rescue Exception => e @incoming_email.update_columns(error: e.class.name) if @incoming_email - delete_staged_users + delete_created_staged_users raise end end @@ -698,11 +699,9 @@ module Email end end - def find_or_create_user(email, display_name, raise_on_failed_create: false) - user = nil - + def find_or_create_user(email, display_name, raise_on_failed_create: false, user: nil) User.transaction do - user = User.find_by_email(email) + user ||= User.find_by_email(email) if user.nil? && SiteSetting.enable_staged_users raise EmailNotAllowed unless EmailValidator.allowed?(email) @@ -715,12 +714,14 @@ module Email name: display_name.presence || User.suggest_name(email), staged: true ) - @staged_users << user + @created_staged_users << user rescue PG::UniqueViolation, ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid raise if raise_on_failed_create user = nil end end + + @staged_users << user if user&.staged? end user @@ -1358,6 +1359,8 @@ module Email end def add_other_addresses(post, sender, mail_object) + max_staged_users_post = nil + %i(to cc bcc).each do |d| next if mail_object[d].blank? @@ -1367,18 +1370,22 @@ module Email email = address_field.address.downcase display_name = address_field.display_name.try(:to_s) next unless email["@"] + if should_invite?(email) - user = find_or_create_user(email, display_name) + user = User.find_by_email(email) + + # cap number of staged users created per email + if (!user || user.staged) && @staged_users.count >= SiteSetting.maximum_staged_users_per_email + max_staged_users_post ||= post.topic.add_moderator_post(sender, I18n.t("emails.incoming.maximum_staged_user_per_email_reached"), import_mode: @opts[:import_mode]) + next + end + + user = find_or_create_user(email, display_name, user: user) if user && can_invite?(post.topic, user) post.topic.topic_allowed_users.create!(user_id: user.id) TopicUser.auto_notification_for_staging(user.id, post.topic_id, TopicUser.notification_reasons[:auto_watch]) post.topic.add_small_action(sender, "invited_user", user.username, import_mode: @opts[:import_mode]) end - # cap number of staged users created per email - if @staged_users.count > SiteSetting.maximum_staged_users_per_email - post.topic.add_moderator_post(sender, I18n.t("emails.incoming.maximum_staged_user_per_email_reached"), import_mode: @opts[:import_mode]) - return - end end rescue ActiveRecord::RecordInvalid, EmailNotAllowed # don't care if user already allowed or the user's email address is not allowed @@ -1413,8 +1420,8 @@ module Email end end - def delete_staged_users - @staged_users.each do |user| + def delete_created_staged_users + @created_staged_users.each do |user| if @incoming_email.user&.id == user.id @incoming_email.update_columns(user_id: nil) end diff --git a/spec/lib/email/receiver_spec.rb b/spec/lib/email/receiver_spec.rb index b12aea30caa..26de87b9d45 100644 --- a/spec/lib/email/receiver_spec.rb +++ b/spec/lib/email/receiver_spec.rb @@ -879,7 +879,17 @@ RSpec.describe Email::Receiver do it "cap the number of staged users created per email" do SiteSetting.maximum_staged_users_per_email = 1 - expect { process(:cc) }.to change(Topic, :count) + expect { process(:cc) }.to change(Topic, :count).by(1) + .and change(User, :count).by(1) + expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action]) + end + + it "cap the number of staged users existing per email" do + Fabricate(:user, email: "discourse@bar.com", staged: true) # from + Fabricate(:user, email: "someone@else.com", staged: true) # to + + SiteSetting.maximum_staged_users_per_email = 1 + expect { process(:cc) }.to change(Topic, :count).and not_change(User, :count) expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action]) end