From 27081708b1222bd56ee148a8a88ff560d59750db Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sun, 30 Aug 2015 13:58:05 +0200 Subject: [PATCH] Added email channel backend validation. --- app/controllers/channels_controller.rb | 201 ------------------------- app/models/channel.rb | 11 +- app/models/channel/assets.rb | 54 +++++++ app/models/channel/driver/smtp.rb | 30 +++- config/routes/channel.rb | 3 - lib/email_helper.rb | 28 ++++ lib/email_helper/probe.rb | 36 ++--- lib/email_helper/verify.rb | 20 ++- test/integration/email_helper_test.rb | 31 ++++ 9 files changed, 176 insertions(+), 238 deletions(-) create mode 100644 app/models/channel/assets.rb diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index f35a30a10..485320a24 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -5,198 +5,6 @@ class ChannelsController < ApplicationController =begin -Resource: -GET /api/v1/channels/#{id}.json - -Response example 1: - -{ - "id":1, - "area":"Email::Account", - "group_id:": 1, - "options":{ - "inbound": { - "adapter":"IMAP", - "options": { - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true - }, - "outbound":{ - "adapter":"SMTP", - "options": { - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "start_tls":true - } - }, - "active":true, - "updated_at":"2012-09-14T17:51:53Z", - "created_at":"2012-09-14T17:51:53Z", - "updated_by_id":2. - "created_by_id":2, -} - -Response example 2: - -{ - "id":1, - "area":"Twitter::Account", - "group_id:": 1, - "options":{ - "adapter":"Twitter", - "auth": { - "consumer_key":"PJ4c3dYYRtSZZZdOKo8ow", - "consumer_secret":"ggAdnJE2Al1Vv0cwwvX5bdvKOieFs0vjCIh5M8Dxk", - "oauth_token":"293437546-xxRa9g74CercnU5AvY1uQwLLGIYrV1ezYtpX8oKW", - "oauth_token_secret":"ju0E4l9OdY2Lh1iTKMymAu6XVfOaU2oGxmcbIMRZQK4", - }, - "sync":{ - "search":[ - { - "term":"#otrs", - "type": "mixed", # optional, possible 'mixed' (default), 'recent', 'popular' - "group_id:": 1, - "limit": 1, # optional - }, - { - "term":"#zombie23", - "group_id:": 2, - }, - { - "term":"#otterhub", - "group_id:": 3, - } - ], - "mentions" { - "group_id:": 4, - "limit": 100, # optional - }, - "direct_messages": { - "group_id:": 4, - "limit": 1, # optional - } - } - }, - "active":true, - "updated_at":"2012-09-14T17:51:53Z", - "created_at":"2012-09-14T17:51:53Z", - "updated_by_id":2. - "created_by_id":2, -} - -Test: -curl http://localhost/api/v1/channels/#{id}.json -v -u #{login}:#{password} - -=end - - def show - return if deny_if_not_role(Z_ROLENAME_ADMIN) - return if !check_access - model_show_render(Channel, params) - end - -=begin - -Resource: -POST /api/v1/channels.json - -Payload: -{ - "area":"Email::Account", - "group_id:": 1, - "options":{ - "inbound": - "adapter":"IMAP", - "options":{ - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true - }, - }, - "outbound":{ - "adapter":"SMTP", - "options": { - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "start_tls":true - } - }, - "active":true, -} - -Response: -{ - "area":"Email::Account", - ... -} - -Test: -curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"name": "some_name","active": true, "note": "some note"}' - -=end - - def create - return if deny_if_not_role(Z_ROLENAME_ADMIN) - model_create_render(Channel, params) - end - -=begin - -Resource: -PUT /api/v1/channels/{id}.json - -Payload: -{ - "id":1, - "area":"Email::Account", - "group_id:": 1, - "options":{ - "inbound": - "adapter":"IMAP", - "options":{ - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true - }, - }, - "outbound":{ - "adapter":"SMTP", - "options": { - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "start_tls":true - } - }, - "active":true, -} - -Response: -{ - "id": 1, - "name": "some_name", - ... -} - -Test: -curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}' - -=end - - def update - return if deny_if_not_role(Z_ROLENAME_ADMIN) - return if !check_access - model_update_render(Channel, params) - end - -=begin - Resource: DELETE /api/v1/channels/{id}.json @@ -405,15 +213,6 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten adapter = params[:adapter].downcase - # validate adapter - if adapter !~ /^(smtp|sendmail)$/ - render json: { - result: 'failed', - message: "Unknown adapter '#{adapter}'", - } - return - end - email = Setting.get('notification_sender') # connection test diff --git a/app/models/channel.rb b/app/models/channel.rb index 46d10b1dc..e3892d22b 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -1,6 +1,9 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ class Channel < ApplicationModel + load 'channel/assets.rb' + include Channel::Assets + store :options store :preferences @@ -34,9 +37,7 @@ fetch one account adapter = options[:adapter] adapter_options = options - if options[:options] - adapter_options = options[:options] - elsif options[:inbound] && options[:inbound][:adapter] + if options[:inbound] && options[:inbound][:adapter] adapter = options[:inbound][:adapter] adapter_options = options[:inbound][:options] end @@ -79,9 +80,7 @@ send via account adapter = options[:adapter] adapter_options = options - if options[:options] - adapter_options = options[:options] - elsif options[:outbound] && options[:outbound][:adapter] + if options[:outbound] && options[:outbound][:adapter] adapter = options[:outbound][:adapter] adapter_options = options[:outbound][:options] end diff --git a/app/models/channel/assets.rb b/app/models/channel/assets.rb new file mode 100644 index 000000000..81aabda9a --- /dev/null +++ b/app/models/channel/assets.rb @@ -0,0 +1,54 @@ +# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ + +class Channel + module Assets + +=begin + +get all assets / related models for this channel + + channel = Channel.find(123) + result = channel.assets( assets_if_exists ) + +returns + + result = { + :channels => { + 123 => channel_model_123, + 1234 => channel_model_1234, + } + } + +=end + + def assets (data = {}) + + if !data[ self.class.to_app_model ] + data[ self.class.to_app_model ] = {} + end + 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') + end + } + + data[ self.class.to_app_model ][ id ] = attributes + end + + + return data if !self['created_by_id'] && !self['updated_by_id'] + %w(created_by_id updated_by_id).each {|local_user_id| + next if !self[ local_user_id ] + next if data[ User.to_app_model ] && data[ User.to_app_model ][ self[ local_user_id ] ] + user = User.lookup( id: self[ local_user_id ] ) + data = user.assets( data ) + } + data + end + + end +end diff --git a/app/models/channel/driver/smtp.rb b/app/models/channel/driver/smtp.rb index 9ae7e2dcd..1aac4bbc1 100644 --- a/app/models/channel/driver/smtp.rb +++ b/app/models/channel/driver/smtp.rb @@ -1,20 +1,44 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ class Channel::Driver::Smtp + +=begin + + instance = Channel::Driver::Smtp.new + instance.send( + host: 'some.host', + port: 25, + enable_starttls_auto: true, # optional + user: 'someuser', + password: 'somepass' + ) + +=end + def send(options, attr, notification = false) # return if we run import mode return if Setting.get('import_mode') + # set smtp defaults + if !options.key?(:port) + options[:port] = 25 + end + if !options.key?(:enable_starttls_auto) + options[:enable_starttls_auto] = true + end + if !options.key?(:openssl_verify_mode) + options[:openssl_verify_mode] = 'none' + end mail = Channel::EmailBuild.build(attr, notification) mail.delivery_method :smtp, { - openssl_verify_mode: 'none', + openssl_verify_mode: options[:openssl_verify_mode], address: options[:host], - port: options[:port] || 25, + port: options[:port], domain: options[:host], user_name: options[:user], password: options[:password], - enable_starttls_auto: true, + enable_starttls_auto: options[:enable_starttls_auto], } mail.deliver end diff --git a/config/routes/channel.rb b/config/routes/channel.rb index 8f0612d00..0e52c5b31 100644 --- a/config/routes/channel.rb +++ b/config/routes/channel.rb @@ -10,9 +10,6 @@ Zammad::Application.routes.draw do match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post # channels - match api_path + '/channels/:id', to: 'channels#show', via: :get - match api_path + '/channels', to: 'channels#create', via: :post - match api_path + '/channels/:id', to: 'channels#update', via: :put match api_path + '/channels/:id', to: 'channels#destroy', via: :delete end diff --git a/lib/email_helper.rb b/lib/email_helper.rb index aa7d6dcea..3de3e77c2 100644 --- a/lib/email_helper.rb +++ b/lib/email_helper.rb @@ -2,6 +2,34 @@ module EmailHelper =begin +get available driver + + result = EmailHelper.available_driver + +returns + + { + :inbound => ['imap', 'pop3'], + :outbound => ['smtp', 'sendmail'], + } + +=end + + def self.available_driver + if Setting.get('system_online_service') + return { + :inbound => ['imap', 'pop3'], + :outbound => ['smtp'], + } + end + { + :inbound => ['imap', 'pop3'], + :outbound => ['smtp', 'sendmail'], + } + end + +=begin + get mail parts user, domain = EmailHelper.parse_email('somebody@example.com') diff --git a/lib/email_helper/probe.rb b/lib/email_helper/probe.rb index 23596a191..00449a4e3 100644 --- a/lib/email_helper/probe.rb +++ b/lib/email_helper/probe.rb @@ -188,14 +188,17 @@ returns on fail adapter = params[:adapter].downcase + # validate adapter + if !EmailHelper.available_driver[:inbound].include?(adapter) + return { + result: 'failed', + message: "Unknown adapter '#{adapter}'", + } + end + # connection test begin - # validate adapter - if adapter !~ /^(imap|pop3)$/ - fail "Unknown adapter '#{adapter}'" - end - require "channel/driver/#{adapter.to_filename}" driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") @@ -264,6 +267,14 @@ returns on fail adapter = params[:adapter].downcase + # validate adapter + if !EmailHelper.available_driver[:outbound].include?(adapter) + return { + result: 'failed', + message: "Unknown adapter '#{adapter}'", + } + end + # prepare test email if subject mail = { @@ -288,21 +299,6 @@ returns on fail # test connection begin - # validate adapter - if adapter !~ /^(smtp|sendmail)$/ - fail "Unknown adapter '#{adapter}'" - end - - # set smtp defaults - if adapter =~ /^smtp$/ - if !params[:options].key?(:port) - params[:options][:port] = 25 - end - if !params[:options].key?(:ssl) - params[:options][:ssl] = true - end - end - require "channel/driver/#{adapter.to_filename}" driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") diff --git a/lib/email_helper/verify.rb b/lib/email_helper/verify.rb index 2f87ea78a..fe3fec00a 100644 --- a/lib/email_helper/verify.rb +++ b/lib/email_helper/verify.rb @@ -67,6 +67,16 @@ or return result end + # validate adapter + adapter = params[:inbound][:adapter].downcase + if !EmailHelper.available_driver[:inbound].include?(adapter) + return { + result: 'failed', + message: "Unknown adapter '#{adapter}'", + } + return + end + # looking for verify email (1..10).each { sleep 5 @@ -75,11 +85,11 @@ or found = nil begin - if params[:inbound][:adapter] =~ /^imap$/i - found = Channel::Driver::Imap.new.fetch(params[:inbound][:options], self, 'verify', subject) - else - found = Channel::Driver::Pop3.new.fetch(params[:inbound][:options], self, 'verify', subject) - end + 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) rescue => e result = { result: 'invalid', diff --git a/test/integration/email_helper_test.rb b/test/integration/email_helper_test.rb index cc5c7a25b..cf23bf820 100644 --- a/test/integration/email_helper_test.rb +++ b/test/integration/email_helper_test.rb @@ -139,6 +139,20 @@ class EmailHelperTest < ActiveSupport::TestCase test 'z probe_inbound' do + # invalid adapter + result = EmailHelper::Probe.inbound( + adapter: 'imap2', + options: { + host: 'not_existsing_host', + port: 993, + ssl: true, + user: 'some@example.com', + password: 'password', + } + ) + + assert_equal('failed', result[:result]) + # network issues result = EmailHelper::Probe.inbound( adapter: 'imap', @@ -256,6 +270,23 @@ class EmailHelperTest < ActiveSupport::TestCase test 'z probe_outbound' do + # invalid adapter + result = EmailHelper::Probe.outbound( + { + adapter: 'smtp2', + options: { + host: 'not_existsing_host', + port: 25, + start_tls: true, + user: 'some@example.com', + password: 'password', + }, + }, + 'some@example.com', + ) + + assert_equal('failed', result[:result]) + # network issues result = EmailHelper::Probe.outbound( {