notification_email.rb 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Ticket::PerformChanges::Action::NotificationEmail < Ticket::PerformChanges::Action
  3. def self.phase
  4. :after_save
  5. end
  6. def execute(...)
  7. return if recipients_checked.blank?
  8. return if from_email_address.blank?
  9. send_email_notification
  10. end
  11. private
  12. def send_email_notification
  13. begin
  14. security = secure_mailing_notification
  15. rescue Exception::SecureMailing::Failure
  16. return
  17. end
  18. (body, attachments_inline) = article_body
  19. new_article = create_new_article(article_params(body, security))
  20. attachments_inline.each do |attachment|
  21. Store.create!(
  22. object: 'Ticket::Article',
  23. o_id: new_article.id,
  24. data: attachment[:data],
  25. filename: attachment[:filename],
  26. preferences: attachment[:preferences],
  27. )
  28. end
  29. article_clone_attachments(new_article.id)
  30. article_clone_attachments_inline(new_article.id)
  31. end
  32. def create_new_article(params)
  33. new_article = Ticket::Article.new(params)
  34. new_article.history_change_source_attribute(performable, 'created')
  35. new_article.save!
  36. new_article
  37. end
  38. def article_params(body, security)
  39. {
  40. ticket_id: id,
  41. to: recipient_string,
  42. subject: record.subject_build(article_subject),
  43. content_type: 'text/html',
  44. body: body,
  45. internal: execution_data['internal'] || false, # default to public if value was not set
  46. sender: article_sender_system,
  47. type: article_type_email,
  48. preferences: article_preferences(security),
  49. updated_by_id: 1,
  50. created_by_id: 1,
  51. }
  52. end
  53. def article_clone_attachments(new_article_id)
  54. last_article = notification_factory_template_objects[:article]
  55. return if !last_article
  56. return if ActiveModel::Type::Boolean.new.cast(execution_data['include_attachments']) != true || last_article.attachments.blank?
  57. last_article.clone_attachments('Ticket::Article', new_article_id, only_attached_attachments: true)
  58. end
  59. def article_clone_attachments_inline(new_article_id)
  60. last_article = notification_factory_template_objects[:article]
  61. return if !last_article
  62. return if !last_article.should_clone_inline_attachments?
  63. last_article.clone_attachments('Ticket::Article', new_article_id, only_inline_attachments: true)
  64. last_article.should_clone_inline_attachments = false # cancel the temporary flag after cloning
  65. end
  66. def from_email_address
  67. @from_email_address ||= begin
  68. group = record.group
  69. email_address = group.email_address
  70. if !email_address
  71. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because no email address is set for group '#{group.name}'"
  72. nil
  73. elsif !email_address.channel_id
  74. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because no channel is set for email address '#{email_address.email}' (id: #{email_address.id})"
  75. nil
  76. elsif !email_address.channel.active
  77. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because the channel for email address '#{email_address.email} is not active' (id: #{email_address.id})"
  78. nil
  79. else
  80. email_address
  81. end
  82. end
  83. end
  84. def article_subject
  85. NotificationFactory::Mailer.template(
  86. templateInline: execution_data['subject'],
  87. objects: notification_factory_template_objects,
  88. quote: false,
  89. locale: locale,
  90. timezone: timezone,
  91. )
  92. end
  93. def article_body
  94. body = NotificationFactory::Mailer.template(
  95. templateInline: execution_data['body'],
  96. objects: notification_factory_template_objects,
  97. quote: true,
  98. locale: locale,
  99. timezone: timezone,
  100. )
  101. HtmlSanitizer.replace_inline_images(body, id)
  102. end
  103. def article_preferences(security)
  104. preferences = {
  105. perform_origin: origin,
  106. }
  107. if security.present?
  108. preferences[:security] = security
  109. end
  110. preferences
  111. end
  112. def secure_mailing_notification
  113. return if !Setting.get('smime_integration') && !Setting.get('pgp_integration')
  114. security = nil
  115. if Setting.get('smime_integration')
  116. security = secure_mailing_notification_smime
  117. return security if security[:sign][:success] || security[:encryption][:success]
  118. end
  119. return security if !Setting.get('pgp_integration')
  120. secure_mailing_notification_pgp
  121. end
  122. def secure_mailing_notification_smime
  123. security = SecureMailing::SMIME::NotificationOptions.process(**secure_mailing_notification_process_params)
  124. if secure_mailing_notification_result_sign_failing?(security)
  125. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because of missing group #{current_group_name} email #{from_email_address.email} certificate for signing (discarding notification)."
  126. raise Exception::SecureMailing::Failure
  127. end
  128. if secure_mailing_notification_result_encryption_failing?(security)
  129. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because public certificate is not available for encryption (discarding notification)."
  130. raise Exception::SecureMailing::Failure
  131. end
  132. security
  133. end
  134. def secure_mailing_notification_pgp
  135. security = SecureMailing::PGP::NotificationOptions.process(**secure_mailing_notification_process_params)
  136. if secure_mailing_notification_result_sign_failing?(security)
  137. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because of missing group #{current_group_name} email #{email_address.email} PGP key for signing (discarding notification)."
  138. raise Exception::SecureMailing::Failure
  139. end
  140. if secure_mailing_notification_result_encryption_failing?(security)
  141. Rails.logger.info "Unable to send trigger based notification to #{recipient_string} because public PGP keys are not available for encryption (discarding notification)."
  142. raise Exception::SecureMailing::Failure
  143. end
  144. security
  145. end
  146. def secure_mailing_notification_process_params
  147. {
  148. from: from_email_address,
  149. recipients: recipients_checked,
  150. perform: {
  151. sign: secure_mailing_notification_sign,
  152. encrypt: secure_mailing_notification_encryption,
  153. },
  154. }
  155. end
  156. def secure_mailing_notification_result_sign_failing?(result)
  157. secure_mailing_notification_sign && execution_data['sign'] == 'discard' && !result[:sign][:success]
  158. end
  159. def secure_mailing_notification_result_encryption_failing?(result)
  160. secure_mailing_notification_encryption && execution_data['encryption'] == 'discard' && !result[:encryption][:success]
  161. end
  162. def secure_mailing_notification_sign
  163. @secure_mailing_notification_sign ||= execution_data['sign'].present? && execution_data['sign'] != 'no'
  164. end
  165. def secure_mailing_notification_encryption
  166. @secure_mailing_notification_encryption ||= execution_data['encryption'].present? && execution_data['encryption'] != 'no'
  167. end
  168. def recipients_raw
  169. @recipients_raw ||= Array(execution_data['recipient'])
  170. .each_with_object([]) { |recipient_type, sum| sum.concat(Array(recipients_by_type(recipient_type)).compact) }
  171. end
  172. def recipients_checked
  173. @recipients_checked ||= begin
  174. recipients_checked = []
  175. recipients_raw.each do |recipient_email|
  176. recipient_email = valid_recipient_address(recipient_email)
  177. next if recipient_email.blank?
  178. next if recipients_checked.include?(recipient_email)
  179. next if !send_recipient_notification?(recipient_email)
  180. recipients_checked.push(recipient_email)
  181. end
  182. recipients_checked
  183. end
  184. end
  185. def recipient_string
  186. @recipient_string ||= recipients_checked.join(', ')
  187. end
  188. def recipients_by_type(recipient_type)
  189. case recipient_type
  190. when 'article_last_sender'
  191. recipients_by_type_article_last_sender
  192. when 'ticket_customer'
  193. user_lookup_email(record.customer_id)
  194. when 'ticket_owner'
  195. user_lookup_email(record.owner_id)
  196. when 'ticket_agents'
  197. recipients_by_type_user_group_access
  198. when %r{\Auserid_(\d+)\z}
  199. return user_lookup_email($1) if User.exists?($1)
  200. Rails.logger.warn "Can't find configured #{origin} Email recipient User with ID '#{$1}'"
  201. nil
  202. else
  203. Rails.logger.error "Unknown email notification recipient '#{recipient_type}'"
  204. nil
  205. end
  206. end
  207. def recipients_by_type_article_last_sender
  208. return nil if article.blank?
  209. if article.reply_to
  210. article.reply_to
  211. elsif article.from
  212. article.from
  213. elsif article.origin_by_id
  214. user_lookup_email(article.origin_by_id)
  215. elsif article.created_by_id
  216. user_lookup_email(article.created_by_id)
  217. end
  218. end
  219. def recipients_by_type_user_group_access
  220. User.group_access(record.group_id, 'full').sort_by(&:login).map(&:email)
  221. end
  222. def user_lookup_email(id)
  223. User.find_by(id: id).email
  224. end
  225. def send_recipient_notification?(recipient_email)
  226. # do not send notification if system address
  227. return false if EmailAddress.exists?(email: recipient_email)
  228. return false if trigger_based_notification_blocked?(recipient_email)
  229. # do not sent notifications to this recipients or for auto response tagged incoming emails
  230. return false if send_no_auto_response?(recipient_email)
  231. # loop protection / check if maximal count of trigger mail has reached
  232. return false if ticket_trigger_loop_protection?(recipient_email)
  233. true
  234. end
  235. def trigger_based_notification_blocked?(recipient_email)
  236. users = User.where(email: recipient_email)
  237. users.any? do |user|
  238. blocked_in_days = trigger_based_notification_blocked_in_days(user)
  239. if blocked_in_days.zero?
  240. false
  241. else
  242. Rails.logger.info "Send no trigger based notification to #{user.email} because email is marked as mail_delivery_failed for #{blocked_in_days} day(s)"
  243. true
  244. end
  245. end
  246. end
  247. def trigger_based_notification_blocked_in_days(user)
  248. preferences = user.preferences
  249. return 0 if !preferences[:mail_delivery_failed]
  250. return 0 if preferences[:mail_delivery_failed_data].blank?
  251. # Blocked for 60 full days; see #4459.
  252. remaining_days = (preferences[:mail_delivery_failed_data].to_date - Time.zone.now.to_date).to_i + 61
  253. return remaining_days if remaining_days.positive?
  254. # Cleanup the user preferences
  255. trigger_based_notification_reset_blocking(user)
  256. 0
  257. end
  258. def trigger_based_notification_reset_blocking(user)
  259. user.preferences[:mail_delivery_failed] = false
  260. user.preferences[:mail_delivery_failed_data] = nil
  261. user.save!
  262. end
  263. def valid_recipient_address(recipient_email)
  264. begin
  265. Mail::AddressList.new(recipient_email).addresses.each do |address|
  266. recipient_email = address.address
  267. email_address_validation = EmailAddressValidation.new(recipient_email)
  268. return recipient_email.downcase.strip if email_address_validation.valid?
  269. end
  270. rescue
  271. if recipient_email.present?
  272. if recipient_email !~ %r{^(.+?)<(.+?)@(.+?)>$}
  273. return nil # no usable format found
  274. end
  275. recipient_email = "#{$2}@#{$3}".downcase.strip
  276. email_address_validation = EmailAddressValidation.new(recipient_email)
  277. return recipient_email if email_address_validation.valid?
  278. end
  279. end
  280. nil
  281. end
  282. def send_no_auto_response?(recipient_email)
  283. # do not sent notifications to this recipients
  284. begin
  285. return true if recipient_email.match?(%r{#{send_no_auto_response_reg_exp}}i)
  286. rescue => e
  287. Rails.logger.error "Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  288. Rails.logger.error e
  289. return true if recipient_email.match?(%r{(mailer-daemon|postmaster|abuse|root|noreply|noreply.+?|no-reply|no-reply.+?)@.+?}i)
  290. end
  291. auto_response_from_customer?(recipient_email)
  292. end
  293. def send_no_auto_response_reg_exp
  294. @send_no_auto_response_reg_exp ||= Setting.get('send_no_auto_response_reg_exp')
  295. end
  296. def auto_response_from_customer?(recipient_email)
  297. # check if notification should be send because of customer emails
  298. if article.present? && article.preferences.fetch('is-auto-response', false) == true && article.from && article.from =~ %r{#{Regexp.quote(recipient_email)}}i
  299. Rails.logger.info "Send no trigger based notification to #{recipient_email} because of auto response tagged incoming email"
  300. return true
  301. end
  302. false
  303. end
  304. def ticket_trigger_loop_protection?(recipient_email)
  305. ticket_trigger_loop_protection_articles_per_ticket?(recipient_email) || ticket_trigger_loop_protection_articles_total?(recipient_email)
  306. end
  307. def ticket_trigger_loop_protection_articles_per_ticket?(recipient_email)
  308. ticket_trigger_loop_protection_articles_per_ticket_map.each do |minutes, count|
  309. already_sent = Ticket::Article.where(
  310. ticket_id: id,
  311. sender: article_sender_system,
  312. type: article_type_email,
  313. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{SqlHelper.quote_like(recipient_email)}%").count
  314. next if already_sent < count
  315. Rails.logger.error "Send no trigger based notification to #{recipient_email} because already sent #{count} for this ticket within last #{minutes} minutes (loop protection set by setting ticket_trigger_loop_protection_articles_per_ticket)"
  316. return true
  317. end
  318. false
  319. end
  320. def ticket_trigger_loop_protection_articles_per_ticket_map
  321. @ticket_trigger_loop_protection_articles_per_ticket_map ||= Setting.get('ticket_trigger_loop_protection_articles_per_ticket')
  322. end
  323. def ticket_trigger_loop_protection_articles_total?(recipient_email)
  324. ticket_trigger_loop_protection_articles_total_map.each do |minutes, count|
  325. already_sent = Ticket::Article.where(
  326. sender: article_sender_system,
  327. type: article_type_email,
  328. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{SqlHelper.quote_like(recipient_email)}%").count
  329. next if already_sent < count
  330. Rails.logger.error "Send no trigger based notification to #{recipient_email} because already sent #{count} in total within last #{minutes} minutes (loop protection set by setting ticket_trigger_loop_protection_articles_total)"
  331. return true
  332. end
  333. false
  334. end
  335. def ticket_trigger_loop_protection_articles_total_map
  336. @ticket_trigger_loop_protection_articles_total_map ||= Setting.get('ticket_trigger_loop_protection_articles_total')
  337. end
  338. def article_sender_system
  339. @article_sender_system ||= Ticket::Article::Sender.find_by(name: 'System')
  340. end
  341. def article_type_email
  342. @article_type_email ||= Ticket::Article::Type.find_by(name: 'email')
  343. end
  344. def current_group_name
  345. record.group.name
  346. end
  347. end