Remove the inbound message handling to get the Rails upgrade done. Pull requests for re-implementing with ActionMailbox are welcome.

Fixes #2463.
This commit is contained in:
Jyri-Petteri Paloposki 2024-06-06 02:04:29 +03:00
parent 86e36b07a6
commit a94d4fa00b
7 changed files with 1 additions and 477 deletions

View file

@ -1,8 +1,7 @@
class IntegrationsController < ApplicationController
require 'mail'
skip_before_action :login_required, :only => [:cloudmailin, :search_plugin]
skip_before_action :verify_authenticity_token, only: [:cloudmailin]
skip_before_action :login_required, :only => [:search_plugin]
def index
@page_title = 'TRACKS::Integrations'
@ -21,31 +20,8 @@ class IntegrationsController < ApplicationController
.pack('m').gsub(/\n/, '')
end
def cloudmailin
if !verify_cloudmailin_signature
render :body => "Message signature verification failed.", :status => 403
return false
end
if process_message(params[:message])
render :body => 'success', :status => 200
else
render :body => "No user found or other error", :status => 404
end
end
private
def process_message(message)
MessageGateway.receive(Mail.new(message))
end
def verify_cloudmailin_signature
provided = request.request_parameters.delete(:signature)
signature = Digest::MD5.hexdigest(flatten_params(request.request_parameters).sort.map { |k, v| v }.join + SITE_CONFIG['cloudmailin'])
return provided == signature
end
def flatten_params(params, title = nil, result = {})
params.each do |key, value|
if value.is_a? Hash

View file

@ -1,36 +0,0 @@
require 'openssl'
class MailgunController < ApplicationController
skip_before_action :login_required, :only => [:mailgun]
before_action :verify, :only => [:mailgun]
protect_from_forgery with: :null_session
def mailgun
unless params.include? 'body-mime'
Rails.logger.info "Cannot process Mailgun request, no body-mime sent"
render_failure "Unacceptable body-mime", 406
return
end
todo = MessageGateway.receive(params['body-mime'])
if todo
render :xml => todo.to_xml(*todo_xml_params)
else
render_failure "Todo not saved", 406
end
end
private
def verify
unless params['signature'] == OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'),
SITE_CONFIG['mailgun_api_key'],
'%s%s' % [params['timestamp'], params['token']]
)
Rails.logger.info "Cannot verify Mailgun signature"
render_failure "Access denied", 406
return
end
end
end

View file

@ -1,141 +0,0 @@
class MessageGateway < ActionMailer::Base
def receive(email)
user = get_receiving_user_from_email_address(email)
return false if user.nil?
return false unless check_sender_is_in_mailmap(user, email)
context = user.prefs.sms_context
todo_params = get_todo_params(email)
todo_builder = TodoFromRichMessage.new(user, context.id, todo_params[:description], todo_params[:notes])
todo = todo_builder.construct
if todo.save!
Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}"
if attach_email_to_todo(todo, email)
Rails.logger.info "Saved email as attachment to todo for user #{user.login} in context #{context.name}"
end
end
todo
end
private
def attach_email_to_todo(todo, email)
attachment = todo.attachments.build
# create temp file
tmp = Tempfile.new(['attachment', '.eml'], universal_newline: true)
tmp.write email.raw_source.gsub(/\r/, "")
# add temp file to attachment. paperclip will copy the file to the right location
Rails.logger.info "Saved received email to #{tmp.path}"
attachment.file = tmp
tmp.close
saved = attachment.save!
# enable write permissions on group, since MessageGateway could be run under different
# user than Tracks (i.e. apache versus mail)
dir = File.open(File.dirname(attachment.file.path))
dir.chmod(0770)
# delete temp file
tmp.unlink
end
def get_todo_params(email)
params = {}
if email.multipart?
params[:description] = get_text_or_nil(email.subject)
params[:notes] = get_first_text_plain_part(email)
else
if email.subject.blank?
params[:description] = get_decoded_text_or_nil(email.body)
params[:notes] = nil
else
params[:description] = get_text_or_nil(email.subject)
params[:notes] = get_decoded_text_or_nil(email.body)
end
end
params
end
def get_receiving_user_from_email_address(email)
SITE_CONFIG['email_dispatch'] == 'single_user' ? get_receiving_user_from_env_setting : get_receiving_user_from_mail_header(email)
end
def get_receiving_user_from_env_setting
Rails.logger.info "All received email goes to #{ENV['TRACKS_MAIL_RECEIVER']}"
user = User.where(:login => ENV['TRACKS_MAIL_RECEIVER']).first
Rails.logger.info "WARNING: Unknown user set for TRACKS_MAIL_RECEIVER (#{ENV['TRACKS_MAIL_RECEIVER']})" if user.nil?
return user
end
def get_receiving_user_from_mail_header(email)
user = get_receiving_user_from_sms_email(get_address(email))
Rails.logger.info(user.nil? ? "User unknown" : "Email belongs to #{user.login}")
return user
end
def get_address(email)
return SITE_CONFIG['email_dispatch'] == 'to' ? email.to[0] : email.from[0]
end
def get_receiving_user_from_sms_email(address)
Rails.logger.info "Looking for user with email #{address}"
user = User.where("preferences.sms_email" => address.strip).includes(:preference).first
user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first if user.nil?
return user
end
def check_sender_is_in_mailmap(user, email)
if user.present? && !sender_is_in_mailmap?(user, email)
Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}"
return false
end
return true
end
def sender_is_in_mailmap?(user, email)
if (SITE_CONFIG['mailmap'].is_a? Hash) && SITE_CONFIG['email_dispatch'] == 'to'
# Look for the sender in the map of allowed senders
SITE_CONFIG['mailmap'][user.preference.sms_email].include? email.from[0]
else
# We can't check the map if it's not defined, or if the lookup is the
# wrong way round, so just allow it
true
end
end
def get_text_or_nil(text)
return text ? text.strip : nil
end
def get_decoded_text_or_nil(text)
return text ? text.decoded.strip : nil
end
def get_first_text_plain_part(email)
# get all parts from multipart/alternative attachments
parts = get_all_parts(email.parts)
# remove all parts that are not text/plain
parts.reject { |part| !part.content_type.start_with?("text/plain") }
return parts.count > 0 ? parts[0].decoded.strip : ""
end
def get_all_parts(parts)
# return a flattened array of parts. If a multipart attachment is found, recurse over its parts
all_parts = parts.inject([]) do |set, elem|
if elem.content_type.start_with?("multipart/alternative")
# recurse to handle multiparts in this multipart
set += get_all_parts(elem.parts)
else
set << elem
end
end
end
end

View file

@ -4,8 +4,6 @@
<br/><p><%= I18n.t 'integrations.contents_header' %></p>
<ul>
<li><a href="#email-cron-section"><%= I18n.t 'integrations.sections.automatic_email' %></a></li>
<li><a href="#message_gateway"><%= I18n.t 'integrations.sections.message_gateway' %></a></li>
<li><a href="#mailgun"><%= I18n.t 'integrations.sections.mailgun' %></a></li>
<li><a href="#todo_rich_message_format"><%= I18n.t 'integrations.sections.email_rich' %></a></li>
</ul>
<p><%= raw I18n.t 'integrations.add_your_own', tell_us_link: link_to(I18n.t('integrations.tell_us_link_text'), 'https://github.com/TracksApp/tracks/issues') %></p>
@ -19,41 +17,6 @@
<p><%= raw I18n.t 'integrations.cron_2', feeds_link: link_to(I18n.t('integrations.feeds_link_text'), feeds_path) %></p>
<a name="message_gateway"> </a>
<h2><%= I18n.t 'integrations.sections.message_gateway' %></h2>
<p><%= I18n.t 'integrations.message_gateway.description' %></p>
<ul>
<li><%= raw I18n.t 'integrations.message_gateway.instructions.1', preferences_link: link_to(t('layouts.navigation.preferences'), preferences_url), sms_email_name: Preference.human_attribute_name('sms_email'), sms_context_name: Preference.human_attribute_name('sms_context') %></li>
<li><%= raw I18n.t 'integrations.message_gateway.instructions.2', command: "<pre>/PATH/TO/TRACKS/bin/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre>" %></li>
<li><%= I18n.t 'integrations.message_gateway.instructions.3' %></li>
</ul>
<p><%= I18n.t 'integrations.message_gateway.rich_api_tip' %></p>
<p><%= raw I18n.t 'integrations.message_gateway.configuration', site_yml: '<tt>site.yml</tt>', to_name: '<tt>to:</tt>', from_name: '<tt>from:</tt>' %></p>
<p><%= raw I18n.t 'integrations.message_gateway.one_user_configuration', single_user_value: '<tt>single_user</tt>', code: "<pre>TRACKS_MAIL_RECEIVER=" + current_user.login + " /PATH/TO/TRACKS/bin/rails r -e production 'MessageGateway.receive(STDIN.read)'</pre>" %></p>
<a name="mailgun"> </a>
<h2><%= I18n.t 'integrations.sections.mailgun' %></h2>
<p><%= raw I18n.t 'integrations.mailgun.description', mailgun_link: link_to('Mailgun', 'http://www.mailgun.com/') %></p>
<p><%= I18n.t 'integrations.mailgun.conditions' %></p>
<ul>
<li><%= raw I18n.t 'integrations.mailgun.instructions.1', mailgun_link: link_to('Mailgun', 'http://www.mailgun.com/') %></li>
<li><%= I18n.t 'integrations.mailgun.instructions.2' %></li>
<ul>
<li><%= I18n.t 'integrations.mailgun.instructions.2a' %></li>
<li><%= I18n.t 'integrations.mailgun.instructions.2b' %></li>
</ul>
<li><%= raw I18n.t 'integrations.mailgun.instructions.3', preferences_link: link_to(t('layouts.navigation.preferences'), preferences_url), sms_email_name: Preference.human_attribute_name('sms_email') %></li>
<li><%= I18n.t 'integrations.mailgun.instructions.4', sms_context_name: Preference.human_attribute_name('sms_context') %></li>
<li><%= I18n.t 'integrations.mailgun.instructions.5' %></li>
<li><%= raw I18n.t 'integrations.mailgun.instructions.6', code: '<pre class=code>
mailmap:
tracks@user.mailgun.org:
- me@myhome.example.net
- mr.user@work.example.com
</pre>' %></li>
</ul>
<p><%= I18n.t 'integrations.mailgun.gateway_instructions' %></p>
<a name="todo_rich_message_format"> </a>
<h2><%= I18n.t 'integrations.sections.email_rich' %></h2>
<p><%= I18n.t 'integrations.email_rich.description' %></p>

View file

@ -13,65 +13,4 @@ class IntegrationsControllerTest < ActionController::TestCase
assert_response :success
end
def test_cloudmailin_integration_success
stub_site_config do
SITE_CONFIG['cloudmailin'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'from'
post :cloudmailin, params: {
"html"=>"",
"plain"=>"asdasd",
"x_to_header"=>"[\"81496ecea21032d35a7a@cloudmailin.net\"]",
"disposable"=>"",
"from"=>"5555555555@tmomail.net",
"signature"=>"e85e908fb893394762047c21e54ce248",
"to"=>"<123123@cloudmailin.net>",
"subject"=>"asd",
"x_cc_header"=>"",
"message"=>"Received: from VMBX103.ihostexchange.net ([192.168.3.3]) by\r\n HUB103.ihostexchange.net ([66.46.182.53]) with mapi; Wed, 5 Oct 2011 17:12:44\r\n -0400\r\nFrom: SMS User <5555555555@tmomail.net>\r\nTo: Tracks <123123@cloudmailin.net>\r\nDate: Wed, 5 Oct 2011 17:12:43 -0400\r\nSubject: asd\r\nThread-Topic: asd\r\nThread-Index: AcyDo4aig2wghvcsTAOkleWqi4t/FQ==\r\nMessage-ID: <7D7CB176-7559-4997-A301-8DF9726264C7@tmomail.net>\r\nAccept-Language: de-DE, en-US\r\nContent-Language: en-US\r\nX-MS-Has-Attach:\r\nX-MS-TNEF-Correlator:\r\nacceptlanguage: de-DE, en-US\r\nContent-Type: text/plain; charset=\"us-ascii\"\r\nContent-Transfer-Encoding: quoted-printable\r\nMIME-Version: 1.0\r\n\r\nasdasd\r\n"
}
assert_response :success
end
end
def test_cloudmailin_integration_invalid_signature
stub_site_config do
SITE_CONFIG['cloudmailin'] = "12345678901234567890"
post :cloudmailin, params: {
"html"=>"",
"plain"=>"asdasd",
"x_to_header"=>"[\"81496ecea21032d35a7a@cloudmailin.net\"]",
"disposable"=>"",
"from"=>"5555555555@tmomail.net",
"signature"=>"e85e908fb893394762047c21e54ce248",
"to"=>"<123123@cloudmailin.net>",
"subject"=>"asd",
"x_cc_header"=>"",
"message"=>"Received: from VMBX103.ihostexchange.net ([192.168.3.3]) by\r\n HUB103.ihostexchange.net ([66.46.182.53]) with mapi; Wed, 5 Oct 2011 17:12:44\r\n -0400\r\nFrom: SMS User <5555555555@tmomail.net>\r\nTo: Tracks <123123@cloudmailin.net>\r\nDate: Wed, 5 Oct 2011 17:12:43 -0400\r\nSubject: asd\r\nThread-Topic: asd\r\nThread-Index: AcyDo4aig2wghvcsTAOkleWqi4t/FQ==\r\nMessage-ID: <7D7CB176-7559-4997-A301-8DF9726264C7@tmomail.net>\r\nAccept-Language: de-DE, en-US\r\nContent-Language: en-US\r\nX-MS-Has-Attach:\r\nX-MS-TNEF-Correlator:\r\nacceptlanguage: de-DE, en-US\r\nContent-Type: text/plain; charset=\"us-ascii\"\r\nContent-Transfer-Encoding: quoted-printable\r\nMIME-Version: 1.0\r\n\r\nasdasd\r\n"
}
assert_response 403
end
end
def test_cloudmailin_integration_unknown_address
stub_site_config do
SITE_CONFIG['cloudmailin'] = "123456789"
post :cloudmailin, params: {
"html"=>"",
"plain"=>"asdasd",
"x_to_header"=>"[\"81496ecea21032d35a7a@cloudmailin.net\"]",
"disposable"=>"",
"from"=>"444444444444@tmomail.net",
"signature"=>"6d2df0e807bfa9b77d24c31dce6d4515",
"to"=>"<123123@cloudmailin.net>",
"subject"=>"asd",
"x_cc_header"=>"",
"message"=>"Received: from VMBX103.ihostexchange.net ([192.168.3.3]) by\r\n HUB103.ihostexchange.net ([66.46.182.53]) with mapi; Wed, 5 Oct 2011 17:12:44\r\n -0400\r\nFrom: SMS User <444444444444@tmomail.net>\r\nTo: Tracks <123123@cloudmailin.net>\r\nDate: Wed, 5 Oct 2011 17:12:43 -0400\r\nSubject: asd\r\nThread-Topic: asd\r\nThread-Index: AcyDo4aig2wghvcsTAOkleWqi4t/FQ==\r\nMessage-ID: <7D7CB176-7559-4997-A301-8DF9726264C7@tmomail.net>\r\nAccept-Language: de-DE, en-US\r\nContent-Language: en-US\r\nX-MS-Has-Attach:\r\nX-MS-TNEF-Correlator:\r\nacceptlanguage: de-DE, en-US\r\nContent-Type: text/plain; charset=\"us-ascii\"\r\nContent-Transfer-Encoding: quoted-printable\r\nMIME-Version: 1.0\r\n\r\nasdasd\r\n"
}
assert_response 404
end
end
end

View file

@ -1,74 +0,0 @@
require 'test_helper'
require 'support/stub_site_config_helper'
class MailgunControllerTest < ActionController::TestCase
include StubSiteConfigHelper
def setup
@user = users(:sms_user)
@inbox = contexts(:inbox)
end
def load_message(filename)
File.read(File.join(Rails.root, 'test', 'fixtures', filename))
end
def test_mailgun_signature_verifies
stub_site_config do
SITE_CONFIG['mailgun_api_key'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'from'
post :mailgun, params: {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message1.txt')
}
assert_response :success
end
end
def test_mailgun_creates_todo_with_mailmap
stub_site_config do
SITE_CONFIG['mailgun_api_key'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'to'
SITE_CONFIG['mailmap'] = {
'5555555555@tmomail.net' => ['incoming@othermail.com', 'notused@foo.org']
}
todo_count = Todo.count
post :mailgun, params: {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message2.txt')
}
assert_response :success
assert_equal(todo_count+1, Todo.count)
message_todo = Todo.where(:description => "test").first
assert_not_nil(message_todo)
assert_equal(@inbox, message_todo.context)
assert_equal(@user, message_todo.user)
end
end
def test_mailgun_signature_fails
stub_site_config do
SITE_CONFIG['mailgun_api_key'] = "invalidkey"
SITE_CONFIG['email_dispatch'] = 'from'
post :mailgun, params: {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message1.txt')
}
assert_response 406
end
end
end

View file

@ -1,103 +0,0 @@
require 'test_helper'
class MessageGatewayTest < ActiveSupport::TestCase
def setup
@user = users(:sms_user)
@inbox = contexts(:inbox)
end
def load_message(filename)
MessageGateway.receive(File.read(File.join(Rails.root, 'test', 'fixtures', filename)))
end
def test_sms_with_no_subject
todo_count = Todo.count
load_message('sample_sms.txt')
# assert some stuff about it being created
assert_equal(todo_count+1, Todo.count)
message_todo = Todo.where(:description => "message_content").first
assert_not_nil(message_todo)
assert_equal(@inbox, message_todo.context)
assert_equal(@user, message_todo.user)
end
def test_mms_with_subject
todo_count = Todo.count
load_message('sample_mms.txt')
# assert some stuff about it being created
assert_equal(todo_count+1, Todo.count)
message_todo = Todo.where(:description => "This is the subject").first
assert_not_nil(message_todo)
assert_equal(@inbox, message_todo.context)
assert_equal(@user, message_todo.user)
assert_equal("This is the message body", message_todo.notes)
end
def test_email_with_winmail_dat
todo_count = Todo.count
load_message('email_with_winmail.txt')
# assert some stuff about it being created
assert_equal(todo_count+1, Todo.count)
end
def test_email_with_multipart_attachments
todo_count = Todo.count
load_message('email_with_multipart.txt')
# assert some stuff about it being created
assert_equal(todo_count+1, Todo.count)
end
def test_no_user
todo_count = Todo.count
badmessage = File.read(File.join(Rails.root, 'test', 'fixtures', 'sample_sms.txt'))
badmessage.gsub!("5555555555", "notauser")
MessageGateway.receive(badmessage)
assert_equal(todo_count, Todo.count)
end
def test_direct_to_context
message = File.read(File.join(Rails.root, 'test', 'fixtures', 'sample_sms.txt'))
valid_context_msg = message.gsub('message_content', 'this is a task @ anothercontext')
invalid_context_msg = message.gsub('message_content', 'this is also a task @ notacontext')
MessageGateway.receive(valid_context_msg)
valid_context_todo = Todo.where(:description => "this is a task").first
assert_not_nil(valid_context_todo)
assert_equal(contexts(:anothercontext), valid_context_todo.context)
MessageGateway.receive(invalid_context_msg)
invalid_context_todo = Todo.where(:description => 'this is also a task').first
assert_not_nil(invalid_context_todo)
assert_equal(@inbox, invalid_context_todo.context)
end
def test_receiving_email_adds_attachment
attachment_count = Attachment.count
load_message('sample_mms.txt')
message_todo = Todo.where(:description => "This is the subject").first
assert_not_nil(message_todo)
assert_equal attachment_count+1, Attachment.count
assert_equal 1,message_todo.attachments.count
orig = File.read(File.join(Rails.root, 'test', 'fixtures', 'sample_mms.txt'))
attachment = File.read(message_todo.attachments.first.file.path)
assert_equal orig, attachment
end
end