Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure single-tenant application support, using the Graph API #6728

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(microsoft): fetch emails using graph api when tenant is set
  • Loading branch information
moxvallix committed Mar 22, 2023
commit e8193b0045dbe5ec8e3c1cf9ba75986fe621a77f
8 changes: 7 additions & 1 deletion app/jobs/inboxes/fetch_imap_email_inboxes_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ class Inboxes::FetchImapEmailInboxesJob < ApplicationJob

def perform
Inbox.where(channel_type: 'Channel::Email').all.each do |inbox|
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if inbox.channel.imap_enabled
next unless inbox.channel.imap_enabled

if ENV.fetch('AZURE_TENANT_ID', false) && inbox.channel.microsoft?
moxvallix marked this conversation as resolved.
Show resolved Hide resolved
::Inboxes::FetchMsGraphEmailsJob.perform_later(inbox.channel)
else
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel)
end
end
end
end
101 changes: 101 additions & 0 deletions app/jobs/inboxes/fetch_ms_graph_emails_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require 'net/imap'

class Inboxes::FetchMsGraphEmailsJob < ApplicationJob
moxvallix marked this conversation as resolved.
Show resolved Hide resolved
queue_as :low

def perform(channel)
process_email_for_channel(channel)
rescue EOFError => e
Rails.logger.error e
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: channel.account).capture_exception
end

private

def should_fetch_email?(channel)
channel.imap_enabled? && channel.microsoft? && !channel.reauthorization_required?
end

def process_email_for_channel(channel)
# fetching email for microsoft provider
fetch_mail_for_channel(channel)

# clearing old failures like timeouts since the mail is now successfully processed
channel.reauthorized!
end

def fetch_mail_for_channel(channel)
return if channel.provider_config['access_token'].blank?

access_token = valid_access_token channel

return unless access_token

graph = graph_authenticate(access_token)

process_mails(graph, channel)
end

def process_mails(graph, channel)
response = graph.get_from_api('me/messages', {}, graph_query)

unless response.is_a?(Net::HTTPSuccess)
channel.authorization_error!
return false
end

json_response = JSON.parse(response.body)
json_response['value'].each do |message|
inbound_mail = Mail.read_from_string retrieve_mail_mime(graph, message['id'])

next if channel.inbox.messages.find_by(source_id: inbound_mail.message_id).present?

process_mail(inbound_mail, channel)
end
end

def retrieve_mail_mime(graph, id)
response = graph.get_from_api("me/messages/#{id}/$value")
return unless response.is_a?(Net::HTTPSuccess)

response.body
end

def graph_authenticate(access_token)
MicrosoftGraphApi.new(access_token)
end

def yesterday
(Time.zone.today - 1).strftime('%FT%TZ')
end

def tomorrow
(Time.zone.today + 1).strftime('%FT%TZ')
end

# Query to replicate the IMAP search used in Inboxes::FetchImapEmailsJob
# Selects the top 1000 records within the given filter, as that is the maximum
# page size for the API. Might need to look into paginating the requests later,
# for inboxes that receive more than 1000 emails a day?
#
# 1. https://learn.microsoft.com/en-us/graph/api/user-list-messages
# 2. https://learn.microsoft.com/en-us/graph/query-parameters
def graph_query
{
'$filter': "receivedDateTime ge #{yesterday} and receivedDateTime le #{tomorrow}",
'$top': '1000', '$select': 'id'
}
end

def process_mail(inbound_mail, channel)
Imap::ImapMailbox.new.process(inbound_mail, channel)
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: channel.account).capture_exception
end

# Making sure the access token is valid for microsoft provider
def valid_access_token(channel)
Microsoft::RefreshOauthTokenService.new(channel: channel).access_token
end
end