mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	Revert "feat: Support Azure single-tenant application using the Graph… (#7436)
This commit is contained in:
		
							
								
								
									
										12
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.env.example
									
									
									
									
									
								
							| @@ -222,20 +222,10 @@ STRIPE_WEBHOOK_SECRET= | |||||||
| # Make sure to follow https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration on the cloud storage after setting this to true. | # Make sure to follow https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration on the cloud storage after setting this to true. | ||||||
| DIRECT_UPLOADS_ENABLED= | DIRECT_UPLOADS_ENABLED= | ||||||
|  |  | ||||||
| # MS OAUTH creds | #MS OAUTH creds | ||||||
| AZURE_APP_ID= | AZURE_APP_ID= | ||||||
| AZURE_APP_SECRET= | AZURE_APP_SECRET= | ||||||
|  |  | ||||||
| ## MS Azure Tenant ID |  | ||||||
| # Set the following id to the id of your Azure 'tenant'. |  | ||||||
| # This will enable single tenant applications to work. |  | ||||||
| # If the following id is set, Chatwoot will use the Microsoft Graph API |  | ||||||
| # to send and receive emails, as that seems to be required for single |  | ||||||
| # tenant applications. |  | ||||||
| # |  | ||||||
| # https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant |  | ||||||
| AZURE_TENANT_ID= |  | ||||||
|  |  | ||||||
| ## Advanced configurations | ## Advanced configurations | ||||||
| ## Change these values to fine tune performance | ## Change these values to fine tune performance | ||||||
| # control the concurrency setting of sidekiq | # control the concurrency setting of sidekiq | ||||||
|   | |||||||
| @@ -4,7 +4,13 @@ class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts | |||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     email = params[:authorization][:email] |     email = params[:authorization][:email] | ||||||
|     redirect_url = microsoft_client.auth_code.authorize_url(auth_params) |     redirect_url = microsoft_client.auth_code.authorize_url( | ||||||
|  |       { | ||||||
|  |         redirect_uri: "#{base_url}/microsoft/callback", | ||||||
|  |         scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile', | ||||||
|  |         prompt: 'consent' | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|     if redirect_url |     if redirect_url | ||||||
|       email = email.downcase |       email = email.downcase | ||||||
|       ::Redis::Alfred.setex(email, Current.account.id, 5.minutes) |       ::Redis::Alfred.setex(email, Current.account.id, 5.minutes) | ||||||
| @@ -19,31 +25,4 @@ class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts | |||||||
|   def check_authorization |   def check_authorization | ||||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? |     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   # SMTP, Pop and IMAP are being deprecated by Outlook. |  | ||||||
|   # https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online |  | ||||||
|   # |  | ||||||
|   # As such, Microsoft has made it a real pain to use them. |  | ||||||
|   # If AZURE_TENANT_ID is set, we will use the MS Graph API instead. |  | ||||||
|   def auth_params |  | ||||||
|     return graph_auth_params if ENV.fetch('AZURE_TENANT_ID', false) |  | ||||||
|  |  | ||||||
|     standard_auth_params |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def standard_auth_params |  | ||||||
|     { |  | ||||||
|       redirect_uri: "#{base_url}/microsoft/callback", |  | ||||||
|       scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile', |  | ||||||
|       prompt: 'consent' |  | ||||||
|     } |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def graph_auth_params |  | ||||||
|     { |  | ||||||
|       redirect_uri: "#{base_url}/microsoft/callback", |  | ||||||
|       scope: 'offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send openid profile', |  | ||||||
|       prompt: 'consent' |  | ||||||
|     } |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ module MicrosoftConcern | |||||||
|     ::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil), |     ::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil), | ||||||
|                          { |                          { | ||||||
|                            site: 'https://login.microsoftonline.com', |                            site: 'https://login.microsoftonline.com', | ||||||
|                            authorize_url: "https://login.microsoftonline.com/#{azure_tenant_id}/oauth2/v2.0/authorize", |                            authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', | ||||||
|                            token_url: "https://login.microsoftonline.com/#{azure_tenant_id}/oauth2/v2.0/token" |                            token_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token' | ||||||
|                          }) |                          }) | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -19,8 +19,4 @@ module MicrosoftConcern | |||||||
|   def base_url |   def base_url | ||||||
|     ENV.fetch('FRONTEND_URL', 'http://localhost:3000') |     ENV.fetch('FRONTEND_URL', 'http://localhost:3000') | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def azure_tenant_id |  | ||||||
|     MicrosoftGraphAuth.azure_tenant_id |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -2,19 +2,8 @@ class Inboxes::FetchImapEmailInboxesJob < ApplicationJob | |||||||
|   queue_as :scheduled_jobs |   queue_as :scheduled_jobs | ||||||
|  |  | ||||||
|   def perform |   def perform | ||||||
|     # check imap_enabled for channel |  | ||||||
|     Inbox.where(channel_type: 'Channel::Email').all.find_each(batch_size: 100) do |inbox| |     Inbox.where(channel_type: 'Channel::Email').all.find_each(batch_size: 100) do |inbox| | ||||||
|       next unless inbox.channel.imap_enabled? |       ::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if inbox.channel.imap_enabled | ||||||
|  |  | ||||||
|       fetch_emails(inbox) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def fetch_emails(inbox) |  | ||||||
|     if inbox.channel.microsoft? && ENV.fetch('AZURE_TENANT_ID', false) |  | ||||||
|       ::Inboxes::FetchMsGraphEmailForTenantJob.perform_later(inbox.channel) |  | ||||||
|     else |  | ||||||
|       ::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,101 +0,0 @@ | |||||||
| require 'net/http' |  | ||||||
|  |  | ||||||
| class Inboxes::FetchMsGraphEmailForTenantJob < ApplicationJob |  | ||||||
|   queue_as :scheduled_jobs |  | ||||||
|  |  | ||||||
|   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 |  | ||||||
| @@ -23,7 +23,6 @@ module ConversationReplyMailerHelper | |||||||
|  |  | ||||||
|   def ms_smtp_settings |   def ms_smtp_settings | ||||||
|     return unless @inbox.email? && @channel.imap_enabled && @inbox.channel.provider == 'microsoft' |     return unless @inbox.email? && @channel.imap_enabled && @inbox.channel.provider == 'microsoft' | ||||||
|     return ms_graph_settings if ENV.fetch('AZURE_TENANT_ID', false) |  | ||||||
|  |  | ||||||
|     smtp_settings = { |     smtp_settings = { | ||||||
|       address: 'smtp.office365.com', |       address: 'smtp.office365.com', | ||||||
| @@ -41,15 +40,6 @@ module ConversationReplyMailerHelper | |||||||
|     @options[:delivery_method_options] = smtp_settings |     @options[:delivery_method_options] = smtp_settings | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def ms_graph_settings |  | ||||||
|     graph_settings = { |  | ||||||
|       token: @channel.provider_config['access_token'] |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @options[:delivery_method] = :microsoft_graph |  | ||||||
|     @options[:delivery_method_options] = graph_settings |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def set_delivery_method |   def set_delivery_method | ||||||
|     return unless @inbox.inbox_type == 'Email' && @channel.smtp_enabled |     return unless @inbox.inbox_type == 'Email' && @channel.smtp_enabled | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| require 'microsoft_graph_delivery_method' |  | ||||||
|  |  | ||||||
| ActionMailer::Base.add_delivery_method :microsoft_graph, MicrosoftGraphDeliveryMethod |  | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| # Simple HTTPS API helper class for interacting with MS Graph. |  | ||||||
| # Uses the standard ruby HTTP library for interacting with the API. |  | ||||||
|  |  | ||||||
| require 'uri' |  | ||||||
| require 'net/http' |  | ||||||
|  |  | ||||||
| class MicrosoftGraphApi |  | ||||||
|   API_VERSION = 'v1.0'.freeze |  | ||||||
|   API_PORT = 443 |  | ||||||
|   API_URL = "https://graph.microsoft.com/#{API_VERSION}".freeze |  | ||||||
|  |  | ||||||
|   def initialize(token) |  | ||||||
|     @token = token |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   # Simple get request to the endpoint |  | ||||||
|   # |  | ||||||
|   # 'queries' are the get variables after the main url |  | ||||||
|   # eg. foo/bar?query=myquery |  | ||||||
|   def get_from_api(endpoint, headers = {}, query = {}) |  | ||||||
|     uri = endpoint_to_uri(endpoint, query) |  | ||||||
|     https = setup_https(uri.host) |  | ||||||
|     request = Net::HTTP::Get.new(uri.request_uri) |  | ||||||
|  |  | ||||||
|     # Assign each header to the request |  | ||||||
|     headers.each { |key, value| request[key.to_s] = value.to_s } |  | ||||||
|     request['Authorization'] = "Bearer #{@token}" |  | ||||||
|  |  | ||||||
|     https.request(request) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   # Simple post request to the endpoint |  | ||||||
|   def post_to_api(endpoint, headers = {}, body = '') |  | ||||||
|     uri = endpoint_to_uri(endpoint) |  | ||||||
|     https = setup_https(uri.host) |  | ||||||
|     request = Net::HTTP::Post.new(uri.path) |  | ||||||
|  |  | ||||||
|     # Assign each header to the request |  | ||||||
|     headers.each { |key, value| request[key.to_s] = value.to_s } |  | ||||||
|     request['Authorization'] = "Bearer #{@token}" |  | ||||||
|  |  | ||||||
|     request.body = body |  | ||||||
|     https.request(request) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   private |  | ||||||
|  |  | ||||||
|   def setup_https(host) |  | ||||||
|     https = Net::HTTP.new(host, API_PORT) |  | ||||||
|     https.use_ssl = true |  | ||||||
|     https |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def endpoint_to_uri(endpoint, query = {}) |  | ||||||
|     endpoint.delete_prefix('/') |  | ||||||
|     uri = URI("#{API_URL}/#{endpoint}") |  | ||||||
|     return uri if query.empty? |  | ||||||
|  |  | ||||||
|     uri.query = URI.encode_www_form(query) |  | ||||||
|     uri |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -9,18 +9,6 @@ require 'omniauth-oauth2' | |||||||
| # Implements an OmniAuth strategy to get a Microsoft Graph | # Implements an OmniAuth strategy to get a Microsoft Graph | ||||||
| # compatible token from Azure AD | # compatible token from Azure AD | ||||||
| class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 | class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 | ||||||
|   # Microsoft Azure Tenant |  | ||||||
|   # For single tenant applications, meant to be used by |  | ||||||
|   # organisations for their own apps, the 'common' endpoint is not allowed. |  | ||||||
|   # If the environment variable 'AZURE_TENANT_ID' is set, |  | ||||||
|   # this will return it's value, otherwise, it will default to 'common'. |  | ||||||
|   # |  | ||||||
|   # The tenant id for your Azure organization can be obtained by |  | ||||||
|   # by accessing 'Tenant properties' from the Azure portal. |  | ||||||
|   def self.azure_tenant_id |  | ||||||
|     ENV.fetch('AZURE_TENANT_ID', 'common') |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   option :name, :microsoft_graph_auth |   option :name, :microsoft_graph_auth | ||||||
|  |  | ||||||
|   DEFAULT_SCOPE = 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send' |   DEFAULT_SCOPE = 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send' | ||||||
| @@ -28,8 +16,8 @@ class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 | |||||||
|   # Configure the Microsoft identity platform endpoints |   # Configure the Microsoft identity platform endpoints | ||||||
|   option :client_options, |   option :client_options, | ||||||
|          site: 'https://login.microsoftonline.com', |          site: 'https://login.microsoftonline.com', | ||||||
|          authorize_url: "/#{azure_tenant_id}/oauth2/v2.0/authorize", |          authorize_url: '/common/oauth2/v2.0/authorize', | ||||||
|          token_url: "/#{azure_tenant_id}/oauth2/v2.0/token" |          token_url: '/common/oauth2/v2.0/token' | ||||||
|  |  | ||||||
|   option :pcke, true |   option :pcke, true | ||||||
|   # Send the scope parameter during authorize |   # Send the scope parameter during authorize | ||||||
|   | |||||||
| @@ -1,26 +0,0 @@ | |||||||
| # Recently (around Feb/Mar 2023), Microsoft has made sending |  | ||||||
| # email through SMTP with Outlook near impossible, at least |  | ||||||
| # for single tenant applications. |  | ||||||
| # |  | ||||||
| # As such, adding a delivery method to use the Microsoft Graph |  | ||||||
| # API allows for emails to be sent again. |  | ||||||
| require 'base64' |  | ||||||
|  |  | ||||||
| class MicrosoftGraphDeliveryMethod |  | ||||||
|   def initialize(config) |  | ||||||
|     @config = config |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def deliver!(mail) |  | ||||||
|     # Create a new API connection, and post the mail to the `me/sendMail` endpoint. |  | ||||||
|     # https://learn.microsoft.com/en-us/graph/api/user-sendmail#example-4-send-a-new-message-using-mime-format |  | ||||||
|  |  | ||||||
|     headers = { |  | ||||||
|       'Content-Type' => 'text/plain' |  | ||||||
|     } |  | ||||||
|     body = Base64.encode64(mail.to_s) |  | ||||||
|  |  | ||||||
|     graph = MicrosoftGraphApi.new(@config[:token]) |  | ||||||
|     graph.post_to_api('me/sendMail', headers, body) |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -43,29 +43,6 @@ RSpec.describe 'Microsoft Authorization API', type: :request do | |||||||
|         expect(response.parsed_body['url']).to eq response_url |         expect(response.parsed_body['url']).to eq response_url | ||||||
|         expect(Redis::Alfred.get(administrator.email)).to eq(account.id.to_s) |         expect(Redis::Alfred.get(administrator.email)).to eq(account.id.to_s) | ||||||
|       end |       end | ||||||
|  |  | ||||||
|       it 'creates a new authorization and returns the redirect url for single tenant' do |  | ||||||
|         with_modified_env AZURE_TENANT_ID: 'azure_tenant_id' do |  | ||||||
|           post "/api/v1/accounts/#{account.id}/microsoft/authorization", |  | ||||||
|                headers: administrator.create_new_auth_token, |  | ||||||
|                params: { email: administrator.email }, |  | ||||||
|                as: :json |  | ||||||
|  |  | ||||||
|           microsoft_service = Class.new { extend MicrosoftConcern } |  | ||||||
|  |  | ||||||
|           response_url = microsoft_service.microsoft_client.auth_code.authorize_url( |  | ||||||
|             { |  | ||||||
|               redirect_uri: "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback", |  | ||||||
|               scope: 'offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send openid profile', |  | ||||||
|               prompt: 'consent' |  | ||||||
|             } |  | ||||||
|           ) |  | ||||||
|           expect(response.parsed_body['url']).to eq response_url |  | ||||||
|         end |  | ||||||
|  |  | ||||||
|         expect(response).to have_http_status(:success) |  | ||||||
|         expect(Redis::Alfred.get(administrator.email)).to eq(account.id.to_s) |  | ||||||
|       end |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -7,12 +7,6 @@ RSpec.describe Inboxes::FetchImapEmailInboxesJob do | |||||||
|                            imap_password: 'password', account: account) |                            imap_password: 'password', account: account) | ||||||
|   end |   end | ||||||
|   let(:email_inbox) { create(:inbox, channel: imap_email_channel, account: account) } |   let(:email_inbox) { create(:inbox, channel: imap_email_channel, account: account) } | ||||||
|   let(:microsoft_imap_email_channel) do |  | ||||||
|     create(:channel_email, provider: 'microsoft', imap_enabled: true, imap_address: 'outlook.office365.com', |  | ||||||
|                            imap_port: 993, imap_login: 'imap@outlook.com', imap_password: 'password', account: account, |  | ||||||
|                            provider_config: { access_token: 'access_token' }) |  | ||||||
|   end |  | ||||||
|   let(:ms_email_inbox) { create(:inbox, channel: microsoft_imap_email_channel, account: account) } |  | ||||||
|  |  | ||||||
|   it 'enqueues the job' do |   it 'enqueues the job' do | ||||||
|     expect { described_class.perform_later }.to have_enqueued_job(described_class) |     expect { described_class.perform_later }.to have_enqueued_job(described_class) | ||||||
| @@ -26,24 +20,4 @@ RSpec.describe Inboxes::FetchImapEmailInboxesJob do | |||||||
|       described_class.perform_now |       described_class.perform_now | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   context 'when microsoft inbox' do |  | ||||||
|     it 'calls fetch ms graph email job for single tenant app' do |  | ||||||
|       stub_request(:get, 'https://graph.microsoft.com/v1.0/me/messages?$filter=receivedDateTime%20ge%202023-05-23T00:00:00Z%20and%20receivedDateTime%20le%202023-05-25T00:00:00Z&$select=id&$top=1000') |  | ||||||
|  |  | ||||||
|       with_modified_env AZURE_TENANT_ID: 'azure_tenant_id' do |  | ||||||
|         expect(Inboxes::FetchMsGraphEmailForTenantJob).to receive(:perform_later).with(microsoft_imap_email_channel).once |  | ||||||
|  |  | ||||||
|         described_class.perform_now |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'calls fetch imap email job for multi tenant app' do |  | ||||||
|       with_modified_env AZURE_TENANT_ID: nil do |  | ||||||
|         expect(Inboxes::FetchImapEmailsJob).to receive(:perform_later).with(microsoft_imap_email_channel).once |  | ||||||
|  |  | ||||||
|         described_class.perform_now |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,68 +0,0 @@ | |||||||
| require 'rails_helper' |  | ||||||
|  |  | ||||||
| RSpec.describe Inboxes::FetchMsGraphEmailForTenantJob do |  | ||||||
|   include ActionMailbox::TestHelper |  | ||||||
|  |  | ||||||
|   let(:account) { create(:account) } |  | ||||||
|   let(:microsoft_imap_email_channel) do |  | ||||||
|     create(:channel_email, provider: 'microsoft', imap_enabled: true, imap_address: 'outlook.office365.com', |  | ||||||
|                            imap_port: 993, imap_login: 'imap@outlook.com', imap_password: 'password', account: account, |  | ||||||
|                            provider_config: { access_token: 'access_token' }) |  | ||||||
|   end |  | ||||||
|   let(:ms_email_inbox) { create(:inbox, channel: microsoft_imap_email_channel, account: account) } |  | ||||||
|   let(:inbound_mail) { create_inbound_email_from_mail(from: 'testemail@gmail.com', to: 'imap@outlook.com', subject: 'Hello!') } |  | ||||||
|   let(:yesterday) { (Time.zone.today - 1).strftime('%FT%TZ') } |  | ||||||
|   let(:tomorrow) { (Time.zone.today + 1).strftime('%FT%TZ') } |  | ||||||
|  |  | ||||||
|   it 'enqueues the job' do |  | ||||||
|     expect { described_class.perform_later }.to have_enqueued_job(described_class) |  | ||||||
|       .on_queue('scheduled_jobs') |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   context 'when imap fetch new emails for microsoft mailer' do |  | ||||||
|     before do |  | ||||||
|       stub_request(:get, "https://graph.microsoft.com/v1.0/me/messages?$filter=receivedDateTime%20ge%20#{yesterday}%20and%20receivedDateTime%20le%20#{tomorrow}&$select=id&$top=1000") |  | ||||||
|         .with( |  | ||||||
|           headers: { |  | ||||||
|             'Accept' => '*/*', |  | ||||||
|             'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', |  | ||||||
|             'Authorization' => 'Bearer access_token', |  | ||||||
|             'User-Agent' => 'Ruby' |  | ||||||
|           } |  | ||||||
|         ) |  | ||||||
|         .to_return(status: 200, body: '{"value":[{"id":"1"}]}', headers: {}) |  | ||||||
|  |  | ||||||
|       stub_request(:get, 'https://graph.microsoft.com/v1.0/me/messages/1/$value') |  | ||||||
|         .with( |  | ||||||
|           headers: { |  | ||||||
|             'Accept' => '*/*', |  | ||||||
|             'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', |  | ||||||
|             'Authorization' => 'Bearer access_token', |  | ||||||
|             'User-Agent' => 'Ruby' |  | ||||||
|           } |  | ||||||
|         ) |  | ||||||
|         .to_return(status: 200, body: '', headers: {}) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'fetch and process all emails' do |  | ||||||
|       ms_imap_email_inbox = double |  | ||||||
|  |  | ||||||
|       with_modified_env AZURE_TENANT_ID: 'azure_tenant_id' do |  | ||||||
|         email = Mail.new do |  | ||||||
|           to 'test@outlook.com' |  | ||||||
|           from 'test@gmail.com' |  | ||||||
|           subject :test.to_s |  | ||||||
|           body 'hello' |  | ||||||
|         end |  | ||||||
|         imap_fetch_mail = Net::IMAP::FetchData.new |  | ||||||
|         imap_fetch_mail.attr = { RFC822: email }.with_indifferent_access |  | ||||||
|  |  | ||||||
|         allow(Mail).to receive(:read_from_string).and_return(inbound_mail) |  | ||||||
|         allow(Imap::ImapMailbox).to receive(:new).and_return(ms_imap_email_inbox) |  | ||||||
|         expect(ms_imap_email_inbox).to receive(:process).with(inbound_mail, microsoft_imap_email_channel).once |  | ||||||
|  |  | ||||||
|         described_class.perform_now(microsoft_imap_email_channel) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| require 'rails_helper' |  | ||||||
| # explicitly requiring since we are loading apms conditionally in application.rb |  | ||||||
| require 'sentry-ruby' |  | ||||||
|  |  | ||||||
| describe MicrosoftGraphApi do |  | ||||||
|   let(:yesterday) { (Time.zone.today - 1).strftime('%FT%TZ') } |  | ||||||
|   let(:tomorrow) { (Time.zone.today + 1).strftime('%FT%TZ') } |  | ||||||
|  |  | ||||||
|   describe '#get_from_api' do |  | ||||||
|     before do |  | ||||||
|       stub_request(:get, "https://graph.microsoft.com/v1.0/me/messages?$filter=receivedDateTime%20ge%20#{yesterday}%20and%20receivedDateTime%20le%20#{tomorrow}&$select=id&$top=1000") |  | ||||||
|         .with( |  | ||||||
|           headers: { |  | ||||||
|             'Accept' => '*/*', |  | ||||||
|             'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', |  | ||||||
|             'Authorization' => 'Bearer access_token', |  | ||||||
|             'User-Agent' => 'Ruby' |  | ||||||
|           } |  | ||||||
|         ) |  | ||||||
|         .to_return(status: 200, body: '{"value":[{"id":"1"}]}', headers: {}) |  | ||||||
|  |  | ||||||
|       stub_request(:get, 'https://graph.microsoft.com/v1.0/me/messages/1/$value') |  | ||||||
|         .with( |  | ||||||
|           headers: { |  | ||||||
|             'Accept' => '*/*', |  | ||||||
|             'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', |  | ||||||
|             'Authorization' => 'Bearer access_token', |  | ||||||
|             'User-Agent' => 'Ruby' |  | ||||||
|           } |  | ||||||
|         ) |  | ||||||
|         .to_return(status: 200, body: '', headers: {}) |  | ||||||
|  |  | ||||||
|       stub_request(:post, 'https://graph.microsoft.com/v1.0/me/sendMail') |  | ||||||
|         .with( |  | ||||||
|           body: 'email body', |  | ||||||
|           headers: { |  | ||||||
|             'Accept' => '*/*', |  | ||||||
|             'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', |  | ||||||
|             'Authorization' => 'Bearer access_token', |  | ||||||
|             'User-Agent' => 'Ruby' |  | ||||||
|           } |  | ||||||
|         ) |  | ||||||
|         .to_return(status: 200, body: 'email body', headers: { 'Content-Type' => 'text/plain' }) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'fetch emails' do |  | ||||||
|       graph_query = { :$filter => "receivedDateTime ge #{yesterday} and receivedDateTime le #{tomorrow}", :$top => '1000', :$select => 'id' } |  | ||||||
|       response = described_class.new('access_token').get_from_api('me/messages', {}, graph_query) |  | ||||||
|  |  | ||||||
|       json_response = JSON.parse(response.body) |  | ||||||
|       expect(json_response['value'][0]['id']).to eq '1' |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'post emails' do |  | ||||||
|       response = described_class.new('access_token').post_to_api('me/sendMail', {}, 'email body') |  | ||||||
|  |  | ||||||
|       expect(response.is_a?(Net::HTTPSuccess)).to be true |  | ||||||
|       expect(response.body).to eq('email body') |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S