FIX: Link up reply to post correctly when emailing group (#13339)

When replying to a user_private_message email originating from
a group PM that does _not_ have a reply key (e.g. when replying
directly to the group's SMTP address), we were mistakenly linking
the new post created from the reply to the OP and the user who
created the topic, based on the first IncomingEmail message ID in
the topic, rather than using the correct reply to user and post number
that the user actually replied to.

We now use the In-Reply-To header to look up the corresponding EmailLog
record when the user who replied was sent a user_private_message email,
and use the post from that as the reply_to_user/post.

This also removes superfluous filtering of incoming_email records. After
already filtering by message_id and then addressed_to_user (which only
returns incoming emails where the to, from, or cc address includes any
of the user's emails), we were filtering again but in the ruby code for
the exact same conditions. After removing this all existing tests still
pass.
This commit is contained in:
Martin Brennan
2021-06-10 15:28:50 +10:00
committed by GitHub
parent 3fefdb1973
commit e9dc88a7b6
6 changed files with 158 additions and 15 deletions

View File

@@ -109,7 +109,7 @@ module Email
# server (e.g. a message_id generated by Gmail) and does not need to
# be updated, because message_ids from the IMAP server are not guaranteed
# to be unique.
return unless discourse_generated_message_id?
return unless discourse_generated_message_id?(@message_id)
incoming_email.update(
imap_uid_validity: @opts[:imap_uid_validity],
@@ -745,23 +745,27 @@ module Email
def create_group_post(group, user, body, elided)
message_ids = Email::Receiver.extract_reply_message_ids(@mail, max_message_id_count: 5)
post_ids = []
# incoming emails with matching message ids, and then cross references
# these with any email addresses for the user vs to/from/cc of the
# incoming emails. in effect, any incoming email record for these
# message ids where the user is involved in any way will be returned
incoming_emails = IncomingEmail.where(message_id: message_ids)
if !group.allow_unknown_sender_topic_replies
incoming_emails = incoming_emails.addressed_to_user(user)
end
post_ids = incoming_emails.pluck(:post_id) || []
incoming_emails = incoming_emails.pluck(:post_id, :from_address, :to_addresses, :cc_addresses)
incoming_emails.each do |post_id, from_address, to_addresses, cc_addresses|
if group.allow_unknown_sender_topic_replies
post_ids << post_id
else
post_ids << post_id if contains_email_address_of_user?(from_address, user) ||
contains_email_address_of_user?(to_addresses, user) ||
contains_email_address_of_user?(cc_addresses, user)
end
# if the user is directly replying to an email send to them from discourse,
# there will be a corresponding EmailLog record, so we can use that as the
# reply post if it exists
if discourse_generated_message_id?(mail.in_reply_to)
post_id_from_email_log = EmailLog.where(message_id: mail.in_reply_to)
.addressed_to_user(user)
.order(created_at: :desc)
.limit(1)
.pluck(:post_id).last
post_ids << post_id_from_email_log
end
if post_ids.any? && post = Post.where(id: post_ids).order(:created_at).last
@@ -989,9 +993,9 @@ module Email
@host ||= Email::Sender.host_for(Discourse.base_url)
end
def discourse_generated_message_id?
!!(@message_id =~ message_id_post_id_regexp) ||
!!(@message_id =~ message_id_topic_id_regexp)
def discourse_generated_message_id?(message_id)
!!(message_id =~ message_id_post_id_regexp) ||
!!(message_id =~ message_id_topic_id_regexp)
end
def message_id_post_id_regexp