microsoft_graph_inbound.rb 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. class Channel::Driver::MicrosoftGraphInbound < Channel::Driver::BaseEmailInbound
  3. # Fetches emails from Microsfot 365 account via Graph API
  4. #
  5. # @param options [Hash]
  6. # @option options [Bool, String] :keep_on_server
  7. # @option options [String] :folder_id to fetch emails from
  8. # @option options [String] :user to login with
  9. # @option options [String] :shared_mailbox optional
  10. # @option options [String] :password Graph API access token
  11. # @option options [String] :auth_type must be XOAUTH2
  12. # @param channel [Channel]
  13. #
  14. # @return [Hash]
  15. #
  16. # {
  17. # result: 'ok',
  18. # fetched: 123,
  19. # notice: 'e. g. message about to big emails in mailbox',
  20. # }
  21. #
  22. # @example
  23. #
  24. # params = {
  25. # user: 'xxx@zammad.onmicrosoft.com',
  26. # password: 'xxx',
  27. # shared_mailbox: 'yyy@zammad.onmicrosoft.com',
  28. # keep_on_server: true,
  29. # auth_type: 'XOAUTH2'
  30. # }
  31. #
  32. # channel = Channel.last
  33. # instance = Channel::Driver::MicrosoftGraphInbound.new
  34. # result = instance.fetch(params, channel)
  35. def fetch(...) # rubocop:disable Lint/UselessMethodDefinition
  36. # fetch() method is defined in superclass, but options are subclass-specific,
  37. # so define it here for documentation purposes.
  38. super
  39. end
  40. # Checks if mailbox has any messages.
  41. # It does not check if email is Zammad verification email or not like other drivers due to Graph API limitations.
  42. # X-Zammad-Verify and X-Zammad-Ignore headers are removed from mails sent via Graph API.
  43. # Thus it's not possible to verify Graph API connection by sending email with such header to yourself.
  44. def check_configuration(options)
  45. setup_connection(options)
  46. _collection, count_all = messages_iterator(false, options)
  47. Rails.logger.info 'check only mode, fetch no emails'
  48. {
  49. result: 'ok',
  50. content_messages: count_all,
  51. }
  52. end
  53. def verify_transport(_options, _verify_string)
  54. raise 'Microsoft Graph email channel is never verified. Thus this method is not implemented.' # rubocop:disable Zammad/DetectTranslatableString
  55. end
  56. def fetch_single_message(message_id, count, count_all) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
  57. message_meta = @graph.get_message_basic_details(message_id)
  58. message_validator = MessageValidator.new(message_meta[:headers], message_meta[:size])
  59. # ignore fresh verify messages
  60. if message_validator.fresh_verify_message?
  61. Rails.logger.info " - ignore message #{count}/#{count_all} - because message has a verify message"
  62. return MessageResult.new(success: false)
  63. end
  64. # ignore already imported
  65. if message_validator.already_imported?(@keep_on_server, @channel)
  66. begin
  67. @graph.mark_message_as_read(message_id)
  68. Rails.logger.info "Ignore message #{count}/#{count_all}, because message message id already imported. Graph API Message ID: #{message_id}."
  69. rescue MicrosoftGraph::ApiError => e
  70. Rails.logger.error "Unable to mark email as read #{count}/#{count_all} from Microsoft Graph server (#{@options[:user]}). Graph API Message ID: #{message_id}. #{e.inspect}"
  71. raise e
  72. end
  73. return MessageResult.new(success: false)
  74. end
  75. # delete email from server after article was created
  76. begin
  77. msg = @graph.get_raw_message(message_id)
  78. rescue MicrosoftGraph::ApiError => e
  79. Rails.logger.error "Unable to fetch email #{count}/#{count_all} from Microsoft Graph server (#{@options[:user]}). Graph API Message ID: #{message_id}. #{e.inspect}"
  80. raise e
  81. end
  82. # do not process too big messages, instead download & send postmaster reply
  83. too_large_info = message_validator.too_large?
  84. if too_large_info
  85. if Setting.get('postmaster_send_reject_if_mail_too_large') == true
  86. info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB) - Graph API Message ID: #{message_id}"
  87. Rails.logger.info info
  88. after_action = [:notice, "#{info}\n"]
  89. process_oversized_mail(@channel, msg)
  90. else
  91. info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB) - Graph API Message ID: #{message_id}"
  92. Rails.logger.info info
  93. return MessageResult.new(success: false, after_action: [:too_large_ignored, "#{info}\n"])
  94. end
  95. else
  96. process(@channel, msg, false)
  97. end
  98. if @keep_on_server
  99. begin
  100. @graph.mark_message_as_read(message_id)
  101. rescue MicrosoftGraph::ApiError => e
  102. Rails.logger.error "Unable to mark email as read #{count}/#{count_all} from Microsoft Graph server (#{@options[:user]}). Graph API Message ID: #{message_id}. #{e.inspect}"
  103. raise e
  104. end
  105. else
  106. begin
  107. @graph.delete_message(message_id)
  108. rescue MicrosoftGraph::ApiError => e
  109. Rails.logger.error "Unable to delete #{count}/#{count_all} from Microsoft Graph server (#{@options[:user]}). Graph API Message ID: #{message_id}. #{e.inspect}"
  110. raise e
  111. end
  112. end
  113. MessageResult.new(success: true, after_action: after_action)
  114. end
  115. def messages_iterator(keep_on_server, options)
  116. if options[:folder_id].present?
  117. folder_id = options[:folder_id]
  118. verify_folder!(folder_id, options)
  119. end
  120. # Taking first page of messages only effectivelly applies 1000-messages-in-one-go limit
  121. messages_details = @graph.list_messages(unread_only: keep_on_server, folder_id:, follow_pagination: false)
  122. ids = messages_details.fetch(:items).pluck(:id)
  123. count = messages_details.fetch(:total_count)
  124. [ids, count]
  125. rescue MicrosoftGraph::ApiError => e
  126. Rails.logger.error "Unable to list emails from Microsoft Graph server (#{options[:user]}): #{e.inspect}"
  127. raise e
  128. end
  129. private
  130. def setup_connection(options)
  131. access_token = options[:password]
  132. mailbox = options[:shared_mailbox].presence || options[:user]
  133. @graph = MicrosoftGraph.new access_token:, mailbox:
  134. end
  135. def verify_folder!(id, options)
  136. @graph.get_message_folder_details(id)
  137. rescue MicrosoftGraph::ApiError => e
  138. raise e if e.error_code != 'ErrorInvalidIdMalformed'
  139. Rails.logger.error "Unable to fetch email from folder at Microsoft Graph/#{options[:user]} Folder does not exist: #{id}"
  140. raise "Microsoft Graph email folder does not exist: #{id}"
  141. end
  142. end