diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb index 2ddfec7bc..e1a9baa99 100644 --- a/app/models/channel/email_parser.rb +++ b/app/models/channel/email_parser.rb @@ -117,32 +117,7 @@ class Channel::EmailParser } # set extra headers - begin - data[:from_email] = Mail::Address.new(from).address - data[:from_local] = Mail::Address.new(from).local - data[:from_domain] = Mail::Address.new(from).domain - data[:from_display_name] = Mail::Address.new(from).display_name || - (Mail::Address.new(from).comments && Mail::Address.new(from).comments[0]) - rescue - from.strip! - if from =~ /^(.+?)<(.+?)@(.+?)>$/ - data[:from_email] = "#{$2}@#{$3}" - data[:from_local] = $2 - data[:from_domain] = $3 - data[:from_display_name] = $1 - else - data[:from_email] = from - data[:from_local] = from - data[:from_domain] = from - end - end - - # do extra decoding because we needed to use field.value - data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name]).to_s - data[:from_display_name].delete!('"') - data[:from_display_name].strip! - data[:from_display_name].gsub!(/^'/, '') - data[:from_display_name].gsub!(/'$/, '') + data = data.merge(Channel::EmailParser.sender_properties(from)) # do extra encoding (see issue#1045) if data[:subject].present? @@ -638,6 +613,39 @@ returns true end + def self.sender_properties(from) + data = {} + + begin + data[:from_email] = Mail::Address.new(from).address + data[:from_local] = Mail::Address.new(from).local + data[:from_domain] = Mail::Address.new(from).domain + data[:from_display_name] = Mail::Address.new(from).display_name || + (Mail::Address.new(from).comments && Mail::Address.new(from).comments[0]) + rescue + from.strip! + if from =~ /^(.+?)<(.+?)@(.+?)>$/ + data[:from_email] = "#{$2}@#{$3}" + data[:from_local] = $2 + data[:from_domain] = $3 + data[:from_display_name] = $1 + else + data[:from_email] = from + data[:from_local] = from + data[:from_domain] = from + end + end + + # do extra decoding because we needed to use field.value + data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name]).to_s + data[:from_display_name].delete!('"') + data[:from_display_name].strip! + data[:from_display_name].gsub!(/^'/, '') + data[:from_display_name].gsub!(/'$/, '') + + data + end + def set_attributes_by_x_headers(item_object, header_name, mail, suffix = false) # loop all x-zammad-hedaer-* headers diff --git a/app/models/channel/filter/reply_to_based_sender.rb b/app/models/channel/filter/reply_to_based_sender.rb new file mode 100644 index 000000000..ed42eabf3 --- /dev/null +++ b/app/models/channel/filter/reply_to_based_sender.rb @@ -0,0 +1,36 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::ReplyToBasedSender + + def self.run(_channel, mail) + + reply_to = mail['reply-to'.to_sym] + return if reply_to.blank? + + setting = Setting.get('postmaster_sender_based_on_reply_to') + return if setting.blank? + + # get properties of reply-to header + result = Channel::EmailParser.sender_properties(reply_to) + + if setting == 'as_sender_of_email' + mail[:from] = reply_to + mail[:from_email] = result[:from_email] + mail[:from_local] = result[:from_local] + mail[:from_domain] = result[:from_domain] + mail[:from_display_name] = result[:from_display_name] + return + end + + if setting == 'as_sender_of_email_use_from_realname' + mail[:from] = reply_to + mail[:from_email] = result[:from_email] + mail[:from_local] = result[:from_local] + mail[:from_domain] = result[:from_domain] + return + end + + Rails.logger.error "Invalid setting value for 'postmaster_sender_based_on_reply_to' -> #{setting.inspect}" + end + +end diff --git a/db/migrate/20170525000001_reply_to_sender_feature.rb b/db/migrate/20170525000001_reply_to_sender_feature.rb new file mode 100644 index 000000000..397dca48e --- /dev/null +++ b/db/migrate/20170525000001_reply_to_sender_feature.rb @@ -0,0 +1,45 @@ +class ReplyToSenderFeature < ActiveRecord::Migration + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'Sender based on Reply-To header', + name: 'postmaster_sender_based_on_reply_to', + area: 'Email::Base', + description: 'Set/overwrite sender/from of email based on reply-to header. Useful to set correct customer if email is received from a third party system on behalf of a customer.', + options: { + form: [ + { + display: '', + null: true, + name: 'postmaster_sender_based_on_reply_to', + tag: 'select', + options: { + '' => '-', + 'as_sender_of_email' => 'Take reply-to header as sender/from of email.', + 'as_sender_of_email_use_from_realname' => 'Take reply-to header as sender/from of email and use realname of origin from.', + }, + }, + ], + }, + state: [], + preferences: { + permission: ['admin.channel_email'], + }, + frontend: false + ) + + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0011_postmaster_sender_based_on_reply_to', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to set the sender/from of emails based on reply-to header.', + options: {}, + state: 'Channel::Filter::ReplyToBasedSender', + frontend: false + ) + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 99c5305c8..53ffb728a 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -1657,6 +1657,33 @@ Setting.create_if_not_exists( frontend: false ) +Setting.create_if_not_exists( + title: 'Sender based on Reply-To header', + name: 'postmaster_sender_based_on_reply_to', + area: 'Email::Base', + description: 'Set/overwrite sender/from of email based on reply-to header. Useful to set correct customer if email is received from a third party system on behalf of a customer.', + options: { + form: [ + { + display: '', + null: true, + name: 'postmaster_sender_based_on_reply_to', + tag: 'select', + options: { + '' => '-', + 'as_sender_of_email' => 'Take reply-to header as sender/from of email.', + 'as_sender_of_email_use_from_realname' => 'Take reply-to header as sender/from of email and use realname of origin from.', + }, + }, + ], + }, + state: [], + preferences: { + permission: ['admin.channel_email'], + }, + frontend: false +) + Setting.create_if_not_exists( title: 'Notification Sender', name: 'notification_sender', @@ -2217,6 +2244,15 @@ Setting.create_if_not_exists( state: 'Channel::Filter::Trusted', frontend: false ) +Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0011_postmaster_sender_based_on_reply_to', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to set the sender/from of emails based on reply-to header.', + options: {}, + state: 'Channel::Filter::ReplyToBasedSender', + frontend: false +) Setting.create_if_not_exists( title: 'Defines postmaster filter.', name: '0012_postmaster_filter_sender_is_system_address', diff --git a/test/unit/email_process_reply_to_test.rb b/test/unit/email_process_reply_to_test.rb new file mode 100644 index 000000000..dbef68207 --- /dev/null +++ b/test/unit/email_process_reply_to_test.rb @@ -0,0 +1,103 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessReplyToTest < ActiveSupport::TestCase + + test 'normal processing' do + + setting_orig = Setting.get('postmaster_sender_based_on_reply_to') + Setting.set('postmaster_sender_based_on_reply_to', '') + + email = "From: Bob Smith +To: zammad@example.com +Subject: some new subject +Reply-To: replay_to_customer_process1@example.com + +Some Text" + + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email) + assert_equal('Bob Smith ', article_p.from) + assert_equal('replay_to_customer_process1@example.com', article_p.reply_to) + assert_equal('marketing_tool@example.com', ticket_p.customer.email) + assert_equal('Bob', ticket_p.customer.firstname) + assert_equal('Smith', ticket_p.customer.lastname) + + Setting.set('postmaster_sender_based_on_reply_to', setting_orig) + + end + + test 'normal processing - take reply to as customer' do + + setting_orig = Setting.get('postmaster_sender_based_on_reply_to') + Setting.set('postmaster_sender_based_on_reply_to', 'as_sender_of_email') + + email = "From: Bob Smith +To: zammad@example.com +Subject: some new subject +Reply-To: replay_to_customer_process2@example.com + +Some Text" + + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email) + assert_equal('replay_to_customer_process2@example.com', article_p.from) + assert_equal('replay_to_customer_process2@example.com', article_p.reply_to) + assert_equal('replay_to_customer_process2@example.com', ticket_p.customer.email) + assert_equal('', ticket_p.customer.firstname) + assert_equal('', ticket_p.customer.lastname) + + email = "From: Bob Smith +To: zammad@example.com +Subject: some new subject +Reply-To: Some Name + +Some Text" + + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email) + assert_equal('Some Name ', article_p.from) + assert_equal('Some Name ', article_p.reply_to) + assert_equal('replay_to_customer_process2-1@example.com', ticket_p.customer.email) + assert_equal('Some', ticket_p.customer.firstname) + assert_equal('Name', ticket_p.customer.lastname) + + Setting.set('postmaster_sender_based_on_reply_to', setting_orig) + + end + + test 'normal processing - take reply to as customer and use from as realname' do + + setting_orig = Setting.get('postmaster_sender_based_on_reply_to') + Setting.set('postmaster_sender_based_on_reply_to', 'as_sender_of_email_use_from_realname') + + email = "From: Bob Smith +To: zammad@example.com +Subject: some new subject +Reply-To: replay_to_customer_process3@example.com + +Some Text" + + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email) + assert_equal('replay_to_customer_process3@example.com', article_p.from) + assert_equal('replay_to_customer_process3@example.com', article_p.reply_to) + assert_equal('replay_to_customer_process3@example.com', ticket_p.customer.email) + assert_equal('Bob', ticket_p.customer.firstname) + assert_equal('Smith', ticket_p.customer.lastname) + + email = "From: Bob Smith +To: zammad@example.com +Subject: some new subject +Reply-To: Some Name + +Some Text" + + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email) + assert_equal('Some Name ', article_p.from) + assert_equal('Some Name ', article_p.reply_to) + assert_equal('replay_to_customer_process3-1@example.com', ticket_p.customer.email) + assert_equal('Bob', ticket_p.customer.firstname) + assert_equal('Smith', ticket_p.customer.lastname) + + Setting.set('postmaster_sender_based_on_reply_to', setting_orig) + + end + +end