From eb0aac89ba500f8d3dd3cd3f49051b6227b809a0 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sun, 6 Sep 2015 09:50:51 +0200 Subject: [PATCH] Improved email channel wizard, show info if there are already emails in the mailbox. --- .../_application_controller_generic.js.coffee | 1 + .../app/controllers/_channel/chat.js.coffee | 26 +++ .../app/controllers/_channel/email.js.coffee | 28 +++- .../controllers/_channel/facebook.js.coffee | 2 + .../app/controllers/_channel/form.js.coffee | 4 +- .../controllers/_channel/twitter.js.coffee | 2 + .../app/controllers/_channel/web.js.coffee | 2 + .../app/controllers/channel.js.coffee | 14 -- .../app/controllers/getting_started.js.coffee | 26 ++- .../channel/email_account_wizard.jst.eco | 27 ++- .../app/views/getting_started/email.jst.eco | 18 +- app/controllers/channels_controller.rb | 8 +- app/models/channel.rb | 6 +- app/models/channel/assets.rb | 19 ++- app/models/channel/driver/imap.rb | 156 ++++++++++++++---- app/models/channel/driver/pop3.rb | 138 +++++++++++++--- lib/email_helper.rb | 27 ++- lib/email_helper/probe.rb | 96 +++++------ lib/email_helper/verify.rb | 13 +- test/integration/email_helper_test.rb | 74 +++++++-- 20 files changed, 527 insertions(+), 160 deletions(-) delete mode 100644 app/assets/javascripts/app/controllers/channel.js.coffee diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee index 556fdaa26..a355a560d 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee @@ -602,6 +602,7 @@ class App.Wizard extends App.Controller goToSlide: (e) => e.preventDefault() slide = $(e.target).data('slide') + return if !slide @showSlide(slide) showSlide: (name) => diff --git a/app/assets/javascripts/app/controllers/_channel/chat.js.coffee b/app/assets/javascripts/app/controllers/_channel/chat.js.coffee index 2bf26c2d0..f4daca886 100644 --- a/app/assets/javascripts/app/controllers/_channel/chat.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/chat.js.coffee @@ -14,3 +14,29 @@ class App.ChannelChat extends App.ControllerTabs ] @render() + +# enable/disable + +# show also if nobody is online / leave a message + +# channels + # sales - en / authentication needed + # sales - de / authentication needed + + + +# agent + # channes I'm work for + + # x sales - de / greeting + # o sales - en / greeting + +# concurent chats + +# active chats + # name, email, location/ip, age + + +# chat history + +App.Config.set( 'Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/_channel/email.js.coffee b/app/assets/javascripts/app/controllers/_channel/email.js.coffee index 51509bebf..1cba9f006 100644 --- a/app/assets/javascripts/app/controllers/_channel/email.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/email.js.coffee @@ -363,7 +363,7 @@ class App.ChannelEmailAccountWizard extends App.Wizard 'submit .js-inbound': 'probeInbound' 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' 'submit .js-outbound': 'probleOutbound' - 'click .js-back': 'goToSlide' + 'click .js-goToSlide': 'goToSlide' 'click .js-close': 'hide' constructor: -> @@ -488,7 +488,20 @@ class App.ChannelEmailAccountWizard extends App.Wizard if data.setting for key, value of data.setting @account[key] = value - @verify(@account) + + if !@channel && data.content_messages && data.content_messages > 0 + message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) + @$('.js-inbound-acknowledge .js-message').html(message) + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro') + @$('.js-inbound-acknowledge .js-next').attr('data-slide', '') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify').bind('click.verify', (e) => + e.preventDefault() + @verify(@account) + ) + @showSlide('js-inbound-acknowledge') + else + @verify(@account) + else if data.result is 'duplicate' @showSlide('js-intro') @showAlert('js-intro', 'Account already exists!' ) @@ -530,7 +543,14 @@ class App.ChannelEmailAccountWizard extends App.Wizard # remember account settings @account.inbound = params - @showSlide('js-outbound') + if !@channel && data.content_messages && data.content_messages > 0 + message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) + @$('.js-inbound-acknowledge .js-message').html(message) + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify') + @showSlide('js-inbound-acknowledge') + else + @showSlide('js-outbound') # fill user / password based on inbound settings if !@channel @@ -768,3 +788,5 @@ class App.ChannelEmailNotificationWizard extends App.Wizard @showInvalidField('js-outbound', data.invalid_field) @enable(e) ) + +App.Config.set( 'Email', { prio: 3000, name: 'Email', parent: '#channels', target: '#channels/email', controller: App.ChannelEmail, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/_channel/facebook.js.coffee b/app/assets/javascripts/app/controllers/_channel/facebook.js.coffee index 7de3a7cda..8ec5d5c9b 100644 --- a/app/assets/javascripts/app/controllers/_channel/facebook.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/facebook.js.coffee @@ -12,3 +12,5 @@ class App.ChannelFacebook extends App.Controller @html App.view('channel/facebook')( head: 'some header' ) + +App.Config.set( 'Facebook', { prio: 6000, name: 'Facebook', parent: '#channels', target: '#channels/facebook', controller: App.ChannelFacebook, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/_channel/form.js.coffee b/app/assets/javascripts/app/controllers/_channel/form.js.coffee index 859c5675a..a3c0b03cf 100644 --- a/app/assets/javascripts/app/controllers/_channel/form.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/form.js.coffee @@ -30,4 +30,6 @@ class App.ChannelForm extends App.Controller paramString += " #{key}: #{value}" else paramString += " #{key}: '#{quote(value)}'" - @$('.js-modal-params').html(paramString) \ No newline at end of file + @$('.js-modal-params').html(paramString) + +App.Config.set( 'Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/_channel/twitter.js.coffee b/app/assets/javascripts/app/controllers/_channel/twitter.js.coffee index 91ff2f7f5..38a4b53e3 100644 --- a/app/assets/javascripts/app/controllers/_channel/twitter.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/twitter.js.coffee @@ -11,3 +11,5 @@ class App.ChannelTwitter extends App.Controller @html App.view('channel/twitter')( head: 'some header' ) + +App.Config.set( 'Twitter', { prio: 5000, name: 'Twitter', parent: '#channels', target: '#channels/twitter', controller: App.ChannelTwitter, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/_channel/web.js.coffee b/app/assets/javascripts/app/controllers/_channel/web.js.coffee index bdc13c748..e2abdc27e 100644 --- a/app/assets/javascripts/app/controllers/_channel/web.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/web.js.coffee @@ -14,3 +14,5 @@ class App.ChannelWeb extends App.ControllerTabs ] @render() + +App.Config.set( 'Web', { prio: 1000, name: 'Web', parent: '#channels', target: '#channels/web', controller: App.ChannelWeb, role: ['Admin'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/channel.js.coffee b/app/assets/javascripts/app/controllers/channel.js.coffee deleted file mode 100644 index 3171311aa..000000000 --- a/app/assets/javascripts/app/controllers/channel.js.coffee +++ /dev/null @@ -1,14 +0,0 @@ -#App.Config.set( 'channels/:target', Index, 'Routes' ) -#App.Config.set( 'channels', Index, 'Routes' ) - -#App.Config.set( 'Channels', { prio: 2500, name: 'Channels', target: '#channels', role: ['Admin'] }, 'NavBarLevel4' ) - -#App.Config.set( 'Channels', { prio: 2500, parent: '#admin', name: 'Channels', target: '#channels', role: ['Admin'] }, 'NavBar' ) - -App.Config.set( 'Web', { prio: 1000, name: 'Web', parent: '#channels', target: '#channels/web', controller: App.ChannelWeb, role: ['Admin'] }, 'NavBarAdmin' ) -App.Config.set( 'Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, role: ['Admin'] }, 'NavBarAdmin' ) -App.Config.set( 'Email', { prio: 3000, name: 'Email', parent: '#channels', target: '#channels/email', controller: App.ChannelEmail, role: ['Admin'] }, 'NavBarAdmin' ) -App.Config.set( 'Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, role: ['Admin'] }, 'NavBarAdmin' ) -App.Config.set( 'Twitter', { prio: 5000, name: 'Twitter', parent: '#channels', target: '#channels/twitter', controller: App.ChannelTwitter, role: ['Admin'] }, 'NavBarAdmin' ) -App.Config.set( 'Facebook', { prio: 6000, name: 'Facebook', parent: '#channels', target: '#channels/facebook', controller: App.ChannelFacebook, role: ['Admin'] }, 'NavBarAdmin' ) - diff --git a/app/assets/javascripts/app/controllers/getting_started.js.coffee b/app/assets/javascripts/app/controllers/getting_started.js.coffee index 59c7b049f..da18471c5 100644 --- a/app/assets/javascripts/app/controllers/getting_started.js.coffee +++ b/app/assets/javascripts/app/controllers/getting_started.js.coffee @@ -613,7 +613,7 @@ class ChannelEmail extends App.Wizard 'submit .js-inbound': 'probeInbound' 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' 'submit .js-outbound': 'probleOutbound' - 'click .js-back': 'goToSlide' + 'click .js-goToSlide': 'goToSlide' constructor: -> super @@ -744,7 +744,20 @@ class ChannelEmail extends App.Wizard if data.setting for key, value of data.setting @account[key] = value - @verify(@account) + + if data.content_messages && data.content_messages > 0 + message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) + @$('.js-inbound-acknowledge .js-message').html(message) + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro') + @$('.js-inbound-acknowledge .js-next').attr('data-slide', '') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify').bind('click.verify', (e) => + e.preventDefault() + @verify(@account) + ) + @showSlide('js-inbound-acknowledge') + else + @verify(@account) + else if data.result is 'duplicate' @showSlide('js-intro') @showAlert('js-intro', 'Account already exists!' ) @@ -781,7 +794,14 @@ class ChannelEmail extends App.Wizard # remember account settings @account.inbound = params - @showSlide('js-outbound') + if data.content_messages && data.content_messages > 0 + message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) + @$('.js-inbound-acknowledge .js-message').html(message) + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify') + @showSlide('js-inbound-acknowledge') + else + @showSlide('js-outbound') # fill user / password based on inbound settings if !@channel diff --git a/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco b/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco index 74c6b8494..c1593121a 100644 --- a/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco +++ b/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco @@ -108,7 +108,7 @@ +
+
+

<%- @T('Email Inbound') %>

+
+ +

<%- @T('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', 'x') %>

+
+
+ <%- @T('Go Back') %> + +
+
+
+
@@ -97,7 +111,7 @@
- <%- @T('Go Back') %> + <%- @T('Go Back') %>
diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index 2a340c83f..772f30c44 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -157,9 +157,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten status_in: 'ok', status_out: 'ok', ) - render json: { - result: 'ok', - } + render json: result return end @@ -202,9 +200,7 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten ) end - render json: { - result: 'ok', - } + render json: result end def email_notification diff --git a/app/models/channel.rb b/app/models/channel.rb index e3892d22b..e5535b4cb 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -52,9 +52,9 @@ fetch one account driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") driver_instance = driver_class.new - driver_instance.fetch(adapter_options, self) - self.status_in = 'ok' - self.last_log_in = '' + result = driver_instance.fetch(adapter_options, self) + self.status_in = result[:result] + self.last_log_in = result[:notice] save rescue => e error = "Can't use Channel::Driver::#{adapter.to_classname}: #{e.inspect}" diff --git a/app/models/channel/assets.rb b/app/models/channel/assets.rb index 283b1c55d..be662ce9a 100644 --- a/app/models/channel/assets.rb +++ b/app/models/channel/assets.rb @@ -29,12 +29,21 @@ returns if !data[ self.class.to_app_model ][ id ] attributes = attributes_with_associations - # remove passwords - %w(inbound outbound).each {|key| - if attributes['options'] && attributes['options'][key] && attributes['options'][key]['options'] - attributes['options'][key]['options'].delete('password') + # remove passwords if use is no admin + access = false + if UserInfo.current_user_id + user = User.lookup(id: UserInfo.current_user_id) + if user.role?('Admin') + access = true end - } + end + if !access + %w(inbound outbound).each {|key| + if attributes['options'] && attributes['options'][key] && attributes['options'][key]['options'] + attributes['options'][key]['options'].delete('password') + end + } + end data[ self.class.to_app_model ][ id ] = attributes end diff --git a/app/models/channel/driver/imap.rb b/app/models/channel/driver/imap.rb index 1c59b7fe0..70f0463fc 100644 --- a/app/models/channel/driver/imap.rb +++ b/app/models/channel/driver/imap.rb @@ -4,6 +4,46 @@ require 'net/imap' class Channel::Driver::Imap < Channel::EmailParser +=begin + +fetch emails from IMAP account + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for) + +returns + + { + result: 'ok', + fetched: 123, + notice: 'e. g. message about to big emails in mailbox', + } + +check if connect to IMAP account is possible, return count of mails in mailbox + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'check') + +returns + + { + result: 'ok', + content_messages: 123, + } + +verify IMAP account, check if search email is in there + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for) + +returns + + { + result: 'ok', # 'verify not ok' + } + +=end + def fetch (options, channel, check_type = '', verify_string = '') ssl = true port = 993 @@ -21,65 +61,118 @@ class Channel::Driver::Imap < Channel::EmailParser end Timeout.timeout(timeout) do - @imap = Net::IMAP.new( options[:host], port, ssl, nil, false ) - end # try LOGIN, if not - try plain begin - @imap.authenticate( 'LOGIN', options[:user], options[:password] ) + @imap.authenticate('LOGIN', options[:user], options[:password]) rescue => e if e.to_s !~ /(unsupported\s(authenticate|authentication)\smechanism|not\ssupported)/i raise e end - @imap.login( options[:user], options[:password] ) + @imap.login(options[:user], options[:password]) end + # select folder if !options[:folder] || options[:folder].empty? @imap.select('INBOX') else - @imap.select( options[:folder] ) - end - if check_type == 'check' - Rails.logger.info 'check only mode, fetch no emails' - disconnect - return - elsif check_type == 'verify' - Rails.logger.info "verify mode, fetch no emails #{verify_string}" + @imap.select(options[:folder]) end message_ids = @imap.search(['ALL']) - count_all = message_ids.count - count = 0 + + # check mode only + if check_type == 'check' + Rails.logger.info 'check only mode, fetch no emails' + content_max_check = 5 + content_messages = 0 + + # check messages + message_ids.each do |message_id| + + message_meta = @imap.fetch(message_id, ['RFC822.HEADER'])[0].attr + + # check how many content messages we have, for notice used + header = message_meta['RFC822.HEADER'] + if header && header !~ /x-zammad-ignore/i + content_messages += 1 + break if content_max_check < content_messages + end + end + if content_messages >= content_messages + content_messages = message_ids.count + end + disconnect + return { + result: 'ok', + content_messages: content_messages, + } + end # reverse message order to increase performance if check_type == 'verify' + Rails.logger.info "verify mode, fetch no emails #{verify_string}" message_ids.reverse! - end - - message_ids.each do |message_id| - count += 1 - Rails.logger.info " - message #{count}/#{count_all}" - #Rails.logger.info msg.to_s # check for verify message - if check_type == 'verify' - subject = @imap.fetch(message_id, 'ENVELOPE')[0].attr['ENVELOPE'].subject + message_ids.each do |message_id| + + message_meta = @imap.fetch(message_id, ['ENVELOPE'])[0].attr + + # check if verify message exists + subject = message_meta['ENVELOPE'].subject if subject && subject =~ /#{verify_string}/ Rails.logger.info " - verify email #{verify_string} found" @imap.store(message_id, '+FLAGS', [:Deleted]) @imap.expunge() disconnect - return 'verify ok' + return { + result: 'ok', + } end - else + end - # delete email from server after article was created - msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822'] - if process(channel, msg) - @imap.store(message_id, '+FLAGS', [:Deleted]) - end + disconnect + return { + result: 'verify not ok', + } + end + + # fetch regular messages + count_all = message_ids.count + count = 0 + count_fetched = 0 + notice = '' + message_ids.each do |message_id| + count += 1 + Rails.logger.info " - message #{count}/#{count_all}" + #Rails.logger.info msg.to_s + + message_meta = @imap.fetch(message_id, ['RFC822.SIZE', 'FLAGS', 'INTERNALDATE'])[0] + + # ignore to big messages + max_message_size = Setting.get('postmaster_max_size') + real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024 + if real_message_size > max_message_size + info = " - ignore message #{count}/#{count_all} - because message is to big (is:#{real_message_size}/max:#{max_message_size} in MB)" + Rails.logger.info info + notice += "#{info}\n" + next + end + + # ignore deleted messages + if message_meta.attr['FLAGS'].include?(:Deleted) + Rails.logger.info " - ignore message #{count}/#{count_all} - because message has already delete flag" + next + end + + # delete email from server after article was created + msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822'] + if process(channel, msg) + @imap.store(message_id, '+FLAGS', [:Deleted]) + count_fetched += 1 end end @imap.expunge() @@ -88,6 +181,11 @@ class Channel::Driver::Imap < Channel::EmailParser Rails.logger.info ' - no message' end Rails.logger.info 'done' + { + result: 'ok', + fetched: count_fetched, + notice: notice, + } end def disconnect diff --git a/app/models/channel/driver/pop3.rb b/app/models/channel/driver/pop3.rb index 253f9b098..554102d5e 100644 --- a/app/models/channel/driver/pop3.rb +++ b/app/models/channel/driver/pop3.rb @@ -4,6 +4,46 @@ require 'net/pop' class Channel::Driver::Pop3 < Channel::EmailParser +=begin + +fetch emails from Pop3 account + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for) + +returns + + { + result: 'ok', + fetched: 123, + notice: 'e. g. message about to big emails in mailbox', + } + +check if connect to Pop3 account is possible, return count of mails in mailbox + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'check') + +returns + + { + result: 'ok', + content_messages: 123, + } + +verify Pop3 account, check if search email is in there + + instance = Channel::Driver::Imap.new + result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for) + +returns + + { + result: 'ok', # 'verify not ok' + } + +=end + def fetch (options, channel, check_type = '', verify_string = '') ssl = true port = 995 @@ -15,8 +55,11 @@ class Channel::Driver::Pop3 < Channel::EmailParser Rails.logger.info "fetching pop3 (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})" @pop = Net::POP3.new( options[:host], port ) + #@pop.set_debug_output $stderr # on check, reduce open_timeout to have faster probing + @pop.open_timeout = 8 + @pop.read_timeout = 12 if check_type == 'check' @pop.open_timeout = 4 @pop.read_timeout = 6 @@ -25,43 +68,87 @@ class Channel::Driver::Pop3 < Channel::EmailParser if ssl @pop.enable_ssl(OpenSSL::SSL::VERIFY_NONE) end - @pop.start( options[:user], options[:password] ) + @pop.start(options[:user], options[:password]) + + mails = @pop.mails + if check_type == 'check' Rails.logger.info 'check only mode, fetch no emails' - disconnect - return - elsif check_type == 'verify' - Rails.logger.info 'verify mode, fetch no emails' - end + content_max_check = 2 + content_messages = 0 - mails = @pop.mails - count = 0 - count_all = mails.size + # check messages + mails.each do |m| + mail = m.pop + next if !mail + + # check how many content messages we have, for notice used + if mail !~ /x-zammad-ignore/i + content_messages += 1 + break if content_max_check < content_messages + end + end + if content_messages >= content_messages + content_messages = mails.count + end + disconnect + return { + result: 'ok', + content_messages: content_messages, + } + end # reverse message order to increase performance if check_type == 'verify' + Rails.logger.info 'verify mode, fetch no emails' mails.reverse! + + # check for verify message + mails.each do |m| + mail = m.pop + next if !mail + + # check if verify message exists + if mail =~ /#{verify_string}/ + Rails.logger.info " - verify email #{verify_string} found" + m.delete + disconnect + return { + result: 'ok', + } + end + end + + return { + result: 'verify not ok', + } end + # fetch regular messages + count_all = mails.size + count = 0 + count_fetched = 0 + notice = '' mails.each do |m| count += 1 Rails.logger.info " - message #{count}/#{count_all}" - # check for verify message - if check_type == 'verify' - mail = m.pop - if mail && mail =~ /#{verify_string}/ - Rails.logger.info " - verify email #{verify_string} found" - m.delete - disconnect - return 'verify ok' - end - else + mail = m.pop - # delete email from server after article was created - if process(channel, m.pop) - m.delete - end + # ignore to big messages + max_message_size = Setting.get('postmaster_max_size') + real_message_size = mail.size.to_f / 1024 / 1024 + if real_message_size > max_message_size + info = " - ignore message #{count}/#{count_all} - because message is to big (is:#{real_message_size}/max:#{max_message_size} in MB)" + Rails.logger.info info + notice += "#{info}\n" + next + end + + # delete email from server after article was created + if process(channel, m.pop) + m.delete + count_fetched += 1 end end disconnect @@ -69,6 +156,11 @@ class Channel::Driver::Pop3 < Channel::EmailParser Rails.logger.info ' - no message' end Rails.logger.info 'done' + { + result: 'ok', + fetched: count_fetched, + notice: notice, + } end def disconnect diff --git a/lib/email_helper.rb b/lib/email_helper.rb index 459965e50..c1b0dd5f4 100644 --- a/lib/email_helper.rb +++ b/lib/email_helper.rb @@ -107,8 +107,8 @@ returns def self.provider(email, password) # check domain based attributes provider_map = { - google: { - domain: 'gmail.com|googlemail.com|gmail.de', + google_imap: { + domain: 'gmail|googlemail|google', inbound: { adapter: 'imap', options: { @@ -153,6 +153,29 @@ returns } }, }, + google_pop3: { + domain: 'gmail|googlemail|google', + inbound: { + adapter: 'pop3', + options: { + host: 'pop.gmail.com', + port: 995, + ssl: true, + user: email, + password: password, + }, + }, + outbound: { + adapter: 'smtp', + options: { + host: 'smtp.gmail.com', + port: 25, + start_tls: true, + user: email, + password: password, + } + }, + }, } provider_map end diff --git a/lib/email_helper/probe.rb b/lib/email_helper/probe.rb index c6298b7f6..043ea099c 100644 --- a/lib/email_helper/probe.rb +++ b/lib/email_helper/probe.rb @@ -51,13 +51,12 @@ returns on fail user, domain = EmailHelper.parse_email(params[:email]) if !user || !domain - result = { + return { result: 'invalid', messages: { email: 'Invalid email.' }, } - return result end # probe provider based settings @@ -73,18 +72,22 @@ returns on fail next if domain_to_check !~ /#{settings[:domain]}/i # probe inbound - result = EmailHelper::Probe.inbound(settings[:inbound]) - return result if result[:result] != 'ok' + Rails.logger.info "INBOUND PROBE PROVIDER: #{settings[:inbound].inspect}" + result_inbound = EmailHelper::Probe.inbound(settings[:inbound]) + Rails.logger.info "INBOUND RESULT PROVIDER: #{result_inbound.inspect}" + next if result_inbound[:result] != 'ok' # probe outbound - result = EmailHelper::Probe.outbound(settings[:outbound], params[:email]) - return result if result[:result] != 'ok' + Rails.logger.info "OUTBOUND PROBE PROVIDER: #{settings[:outbound].inspect}" + result_outbound = EmailHelper::Probe.outbound(settings[:outbound], params[:email]) + Rails.logger.info "OUTBOUND RESULT PROVIDER: #{result_outbound.inspect}" + next if result_outbound[:result] != 'ok' - result = { + return { result: 'ok', + content_messages: result_inbound[:content_messages], setting: settings, } - return result } } @@ -94,25 +97,31 @@ returns on fail inbound_mx = EmailHelper.provider_inbound_mx(user, params[:email], params[:password], mx_records) inbound_guess = EmailHelper.provider_inbound_guess(user, params[:email], params[:password], domain) inbound_map = inbound_mx + inbound_guess - settings = {} + result = { + result: 'ok', + setting: {} + } success = false inbound_map.each {|config| - Rails.logger.info "INBOUND PROBE: #{config.inspect}" - result = EmailHelper::Probe.inbound( config ) - Rails.logger.info "INBOUND RESULT: #{result.inspect}" + Rails.logger.info "INBOUND PROBE GUESS: #{config.inspect}" + result_inbound = EmailHelper::Probe.inbound(config) + Rails.logger.info "INBOUND RESULT GUESS: #{result_inbound.inspect}" - next if result[:result] != 'ok' + next if result_inbound[:result] != 'ok' + + success = true + result[:setting][:inbound] = config + result[:content_messages] = result_inbound[:content_messages] - success = true - settings[:inbound] = config break } + # give up, no possible inbound found if !success - result = { + return { result: 'failed', + reason: 'inbound failed', } - return result end # probe outbound @@ -122,28 +131,26 @@ returns on fail success = false outbound_map.each {|config| - Rails.logger.info "OUTBOUND PROBE: #{config.inspect}" - result = EmailHelper::Probe.outbound( config, params[:email] ) - Rails.logger.info "OUTBOUND RESULT: #{result.inspect}" + Rails.logger.info "OUTBOUND PROBE GUESS: #{config.inspect}" + result_outbound = EmailHelper::Probe.outbound(config, params[:email]) + Rails.logger.info "OUTBOUND RESULT GUESS: #{result_outbound.inspect}" - next if result[:result] != 'ok' + next if result_outbound[:result] != 'ok' - success = true - settings[:outbound] = config + success = true + result[:setting][:outbound] = config break } + # give up, no possible outbound found if !success - result = { + return { result: 'failed', + reason: 'outbound failed', } - return result end - { - result: 'ok', - setting: settings, - } + result end =begin @@ -197,28 +204,25 @@ returns on fail end # connection test + result_inbound = {} begin require "channel/driver/#{adapter.to_filename}" driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") driver_instance = driver_class.new - driver_instance.fetch(params[:options], nil, 'check') + result_inbound = driver_instance.fetch(params[:options], nil, 'check') rescue => e - result = { + return { result: 'invalid', settings: params, message: e.message, message_human: translation(e.message), invalid_field: invalid_field(e.message), } - return result end - result = { - result: 'ok', - } - result + result_inbound end =begin @@ -291,10 +295,13 @@ returns on fail body: "This is a Test Email of Zammad to verify if Zammad can send emails to an external address.\n\nIf you see this email, you can ignore and delete it.", } end - mail['X-Zammad-Ignore'] = 'true' - mail['X-Loop'] = 'yes' - mail['Precedence'] = 'bulk' - mail['Auto-Submitted'] = 'auto-generated' + if subject + mail['X-Zammad-Test-Message'] = subject + end + mail['X-Zammad-Ignore'] = 'true' + mail['X-Loop'] = 'yes' + mail['Precedence'] = 'bulk' + mail['Auto-Submitted'] = 'auto-generated' # test connection begin @@ -318,27 +325,24 @@ returns on fail next if e.message !~ /#{Regexp.escape(key)}/i - result = { + return { result: 'ok', settings: params, notice: e.message, } - return result } end - result = { + return { result: 'invalid', settings: params, message: e.message, message_human: translation(e.message), invalid_field: invalid_field(e.message), } - return result end - result = { + { result: 'ok', } - result end def self.invalid_field(message_backend) diff --git a/lib/email_helper/verify.rb b/lib/email_helper/verify.rb index 1828568df..888009435 100644 --- a/lib/email_helper/verify.rb +++ b/lib/email_helper/verify.rb @@ -81,14 +81,14 @@ or sleep 5 # fetch mailbox - found = nil + fetch_result = nil begin require "channel/driver/#{adapter.to_filename}" driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") driver_instance = driver_class.new - found = driver_instance.fetch(params[:inbound][:options], self, 'verify', subject) + fetch_result = driver_instance.fetch(params[:inbound][:options], self, 'verify', subject) rescue => e result = { result: 'invalid', @@ -101,13 +101,10 @@ or return result end - next if !found - next if found != 'verify ok' - - return { - result: 'ok', - } + next if !fetch_result + next if fetch_result[:result] != 'ok' + return fetch_result } { diff --git a/test/integration/email_helper_test.rb b/test/integration/email_helper_test.rb index d54c05915..0ef8ad585 100644 --- a/test/integration/email_helper_test.rb +++ b/test/integration/email_helper_test.rb @@ -32,18 +32,18 @@ class EmailHelperTest < ActiveSupport::TestCase password = 'some_pw' map = EmailHelper.provider(email, password) - assert_equal('imap', map[:google][:inbound][:adapter]) - assert_equal('imap.gmail.com', map[:google][:inbound][:options][:host]) - assert_equal(993, map[:google][:inbound][:options][:port]) - assert_equal(email, map[:google][:inbound][:options][:user]) - assert_equal(password, map[:google][:inbound][:options][:password]) + assert_equal('imap', map[:google_imap][:inbound][:adapter]) + assert_equal('imap.gmail.com', map[:google_imap][:inbound][:options][:host]) + assert_equal(993, map[:google_imap][:inbound][:options][:port]) + assert_equal(email, map[:google_imap][:inbound][:options][:user]) + assert_equal(password, map[:google_imap][:inbound][:options][:password]) - assert_equal('smtp', map[:google][:outbound][:adapter]) - assert_equal('smtp.gmail.com', map[:google][:outbound][:options][:host]) - assert_equal(25, map[:google][:outbound][:options][:port]) - assert_equal(true, map[:google][:outbound][:options][:start_tls]) - assert_equal(email, map[:google][:outbound][:options][:user]) - assert_equal(password, map[:google][:outbound][:options][:password]) + assert_equal('smtp', map[:google_imap][:outbound][:adapter]) + assert_equal('smtp.gmail.com', map[:google_imap][:outbound][:options][:host]) + assert_equal(25, map[:google_imap][:outbound][:options][:port]) + assert_equal(true, map[:google_imap][:outbound][:options][:start_tls]) + assert_equal(email, map[:google_imap][:outbound][:options][:user]) + assert_equal(password, map[:google_imap][:outbound][:options][:password]) end @@ -431,7 +431,7 @@ class EmailHelperTest < ActiveSupport::TestCase assert_equal('invalid', result[:result]) assert_not(result[:setting]) - # realtest - test I + # realtest - test I, with imap if !ENV['EMAILHELPER_MAILBOX_1'] fail "Need EMAILHELPER_MAILBOX_1 as ENV variable like export EMAILHELPER_MAILBOX_1='unittestemailhelper01@znuny.com:somepass'" end @@ -446,11 +446,26 @@ class EmailHelperTest < ActiveSupport::TestCase assert_equal('arber.znuny.com', result[:setting][:inbound][:options][:host]) assert_equal('arber.znuny.com', result[:setting][:outbound][:options][:host]) + # realtest - test II, gmail with only pop3 + if !ENV['EMAILHELPER_MAILBOX_2'] + fail "Need EMAILHELPER_MAILBOX_2 as ENV variable like export EMAILHELPER_MAILBOX_2='hansb36621@gmail.com:somepass'" + end + mailbox_user = ENV['EMAILHELPER_MAILBOX_2'].split(':')[0] + mailbox_password = ENV['EMAILHELPER_MAILBOX_2'].split(':')[1] + + result = EmailHelper::Probe.full( + email: mailbox_user, + password: mailbox_password, + ) + assert_equal('ok', result[:result]) + assert_equal('pop.gmail.com', result[:setting][:inbound][:options][:host]) + assert_equal('smtp.gmail.com', result[:setting][:outbound][:options][:host]) + end test 'zz verify' do - # realtest - test I + # realtest - test I, with imap if !ENV['EMAILHELPER_MAILBOX_1'] fail "Need EMAILHELPER_MAILBOX_1 as ENV variable like export EMAILHELPER_MAILBOX_1='unittestemailhelper01@znuny.com:somepass'" end @@ -481,6 +496,39 @@ class EmailHelperTest < ActiveSupport::TestCase sender: mailbox_user, ) assert_equal('ok', result[:result]) + + # realtest - test II, gmail with pop3 + if !ENV['EMAILHELPER_MAILBOX_2'] + fail "Need EMAILHELPER_MAILBOX_2 as ENV variable like export EMAILHELPER_MAILBOX_2='hansb36621@gmail.com:somepass'" + end + mailbox_user = ENV['EMAILHELPER_MAILBOX_2'].split(':')[0] + mailbox_password = ENV['EMAILHELPER_MAILBOX_2'].split(':')[1] + user, domain = EmailHelper.parse_email(mailbox_user) + result = EmailHelper::Verify.email( + inbound: { + adapter: 'pop3', + options: { + host: 'pop.gmail.com', + port: 995, + ssl: true, + user: mailbox_user, + password: mailbox_password, + }, + }, + outbound: { + adapter: 'smtp', + options: { + host: 'smtp.gmail.com', + port: 25, + start_tls: true, + user: mailbox_user, + password: mailbox_password, + }, + }, + sender: mailbox_user, + ) + assert_equal('ok', result[:result]) + end end