FIX: IMAP sync email update uniqueness across groups and minor improvements (#10332)

Adds a imap_group_id column to IncomingEmail to deal with an issue where we were trying to update emails in the mailbox, calling IncomingEmail.where(imap_sync: true). However UID and UIDVALIDITY could be the same across accounts. So if group A used IMAP details for Gmail account A, and group B used IMAP details for Gmail account B, and both tried to sync changes to an email with UID of 3 (e.g. changing Labels), one account could affect the other. This even applied to Archiving!

Also in this PR:

* Fix error occurring if we do a uid_fetch and no emails are returned
* Allow for creating labels within the target mailbox (previously we would not do this, only use existing labels)
* Improve consistency for log messages
* Add specs for generic IMAP provider (Gmail specs still to come)
* Add custom archiving support for Gmail
* Only use Message-ID for uniqueness of IncomingEmail if it was generated by us
* Various refactors and improvements
This commit is contained in:
Martin Brennan
2020-08-03 13:10:17 +10:00
committed by GitHub
parent 8a9e4504fe
commit 2920988b3a
9 changed files with 430 additions and 109 deletions

View File

@ -4,6 +4,9 @@ require 'net/imap'
module Imap
module Providers
class WriteDisabledError < StandardError; end
class Generic
def initialize(server, options = {})
@ -65,19 +68,31 @@ module Imap
def open_mailbox(mailbox_name, write: false)
if write
raise 'two-way IMAP sync is disabled' if !SiteSetting.enable_imap_write
if !SiteSetting.enable_imap_write
raise WriteDisabledError.new("Two-way IMAP sync is disabled! Cannot write to inbox.")
end
imap.select(mailbox_name)
else
imap.examine(mailbox_name)
end
@open_mailbox_name = mailbox_name
@open_mailbox_write = write
{
uid_validity: imap.responses['UIDVALIDITY'][-1]
}
end
def emails(uids, fields, opts = {})
imap.uid_fetch(uids, fields).map do |email|
fetched = imap.uid_fetch(uids, fields)
# This will happen if the email does not exist in the provided mailbox.
# It may have been deleted or otherwise moved, e.g. if deleted in Gmail
# it will end up in "[Gmail]/Bin"
return [] if fetched.nil?
fetched.map do |email|
attributes = {}
fields.each do |field|
@ -105,12 +120,16 @@ module Imap
end
def tag_to_label(tag)
labels[tag]
tag
end
def list_mailboxes
imap.list('', '*').map(&:name)
end
def archive(uid)
# do nothing by default, just removing the Inbox label should be enough
end
end
end
end