Added email channel backend validation.

This commit is contained in:
Martin Edenhofer 2015-08-30 13:58:05 +02:00
parent 8c8788b0cc
commit 27081708b1
9 changed files with 176 additions and 238 deletions

View file

@ -5,198 +5,6 @@ class ChannelsController < ApplicationController
=begin =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: Resource:
DELETE /api/v1/channels/{id}.json 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 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') email = Setting.get('notification_sender')
# connection test # connection test

View file

@ -1,6 +1,9 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel < ApplicationModel class Channel < ApplicationModel
load 'channel/assets.rb'
include Channel::Assets
store :options store :options
store :preferences store :preferences
@ -34,9 +37,7 @@ fetch one account
adapter = options[:adapter] adapter = options[:adapter]
adapter_options = options adapter_options = options
if options[:options] if options[:inbound] && options[:inbound][:adapter]
adapter_options = options[:options]
elsif options[:inbound] && options[:inbound][:adapter]
adapter = options[:inbound][:adapter] adapter = options[:inbound][:adapter]
adapter_options = options[:inbound][:options] adapter_options = options[:inbound][:options]
end end
@ -79,9 +80,7 @@ send via account
adapter = options[:adapter] adapter = options[:adapter]
adapter_options = options adapter_options = options
if options[:options] if options[:outbound] && options[:outbound][:adapter]
adapter_options = options[:options]
elsif options[:outbound] && options[:outbound][:adapter]
adapter = options[:outbound][:adapter] adapter = options[:outbound][:adapter]
adapter_options = options[:outbound][:options] adapter_options = options[:outbound][:options]
end end

View file

@ -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

View file

@ -1,20 +1,44 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::Driver::Smtp 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) def send(options, attr, notification = false)
# return if we run import mode # return if we run import mode
return if Setting.get('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 = Channel::EmailBuild.build(attr, notification)
mail.delivery_method :smtp, { mail.delivery_method :smtp, {
openssl_verify_mode: 'none', openssl_verify_mode: options[:openssl_verify_mode],
address: options[:host], address: options[:host],
port: options[:port] || 25, port: options[:port],
domain: options[:host], domain: options[:host],
user_name: options[:user], user_name: options[:user],
password: options[:password], password: options[:password],
enable_starttls_auto: true, enable_starttls_auto: options[:enable_starttls_auto],
} }
mail.deliver mail.deliver
end end

View file

@ -10,9 +10,6 @@ Zammad::Application.routes.draw do
match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post
# channels # 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 match api_path + '/channels/:id', to: 'channels#destroy', via: :delete
end end

View file

@ -2,6 +2,34 @@ module EmailHelper
=begin =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 get mail parts
user, domain = EmailHelper.parse_email('somebody@example.com') user, domain = EmailHelper.parse_email('somebody@example.com')

View file

@ -188,14 +188,17 @@ returns on fail
adapter = params[:adapter].downcase adapter = params[:adapter].downcase
# validate adapter
if !EmailHelper.available_driver[:inbound].include?(adapter)
return {
result: 'failed',
message: "Unknown adapter '#{adapter}'",
}
end
# connection test # connection test
begin begin
# validate adapter
if adapter !~ /^(imap|pop3)$/
fail "Unknown adapter '#{adapter}'"
end
require "channel/driver/#{adapter.to_filename}" require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
@ -264,6 +267,14 @@ returns on fail
adapter = params[:adapter].downcase adapter = params[:adapter].downcase
# validate adapter
if !EmailHelper.available_driver[:outbound].include?(adapter)
return {
result: 'failed',
message: "Unknown adapter '#{adapter}'",
}
end
# prepare test email # prepare test email
if subject if subject
mail = { mail = {
@ -288,21 +299,6 @@ returns on fail
# test connection # test connection
begin 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}" require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")

View file

@ -67,6 +67,16 @@ or
return result return result
end 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 # looking for verify email
(1..10).each { (1..10).each {
sleep 5 sleep 5
@ -75,11 +85,11 @@ or
found = nil found = nil
begin begin
if params[:inbound][:adapter] =~ /^imap$/i require "channel/driver/#{adapter.to_filename}"
found = Channel::Driver::Imap.new.fetch(params[:inbound][:options], self, 'verify', subject)
else driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
found = Channel::Driver::Pop3.new.fetch(params[:inbound][:options], self, 'verify', subject) driver_instance = driver_class.new
end found = driver_instance.fetch(params[:inbound][:options], self, 'verify', subject)
rescue => e rescue => e
result = { result = {
result: 'invalid', result: 'invalid',

View file

@ -139,6 +139,20 @@ class EmailHelperTest < ActiveSupport::TestCase
test 'z probe_inbound' do 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 # network issues
result = EmailHelper::Probe.inbound( result = EmailHelper::Probe.inbound(
adapter: 'imap', adapter: 'imap',
@ -256,6 +270,23 @@ class EmailHelperTest < ActiveSupport::TestCase
test 'z probe_outbound' do 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 # network issues
result = EmailHelper::Probe.outbound( result = EmailHelper::Probe.outbound(
{ {