From 8a2da01d5111ddb8e37d6bd102fca2ca8916f3f3 Mon Sep 17 00:00:00 2001 From: Greg Sutcliffe Date: Sun, 15 Sep 2013 21:17:05 +0100 Subject: [PATCH] Add Mailgun endpoint for receiving email tasks via Mailgun --- app/controllers/mailgun_controller.rb | 38 +++++++++++ app/models/message_gateway.rb | 19 +++++- config/routes.rb | 2 + config/site.yml.tmpl | 15 ++++- .../integrations_controller_test.rb | 1 + test/controllers/mailgun_controller_test.rb | 66 +++++++++++++++++++ test/fixtures/mailgun_message1.txt | 27 ++++++++ test/fixtures/mailgun_message2.txt | 27 ++++++++ 8 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 app/controllers/mailgun_controller.rb create mode 100644 test/controllers/mailgun_controller_test.rb create mode 100644 test/fixtures/mailgun_message1.txt create mode 100644 test/fixtures/mailgun_message2.txt diff --git a/app/controllers/mailgun_controller.rb b/app/controllers/mailgun_controller.rb new file mode 100644 index 00000000..83e6fe78 --- /dev/null +++ b/app/controllers/mailgun_controller.rb @@ -0,0 +1,38 @@ +require 'openssl' + +class MailgunController < ApplicationController + + skip_before_filter :login_required, :only => [:mailgun] + before_filter :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::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 diff --git a/app/models/message_gateway.rb b/app/models/message_gateway.rb index 0b70ca36..e6c3855d 100644 --- a/app/models/message_gateway.rb +++ b/app/models/message_gateway.rb @@ -27,6 +27,7 @@ class MessageGateway < ActionMailer::Base todo = todo_builder.construct todo.save! Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}" + todo end private @@ -49,10 +50,26 @@ class MessageGateway < ActionMailer::Base if user.nil? user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first end + if user.present? and !sender_is_in_mailmap?(user,email) + Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}" + return nil + end Rails.logger.info(!user.nil? ? "Email belongs to #{user.login}" : "User unknown") return user end - + + def sender_is_in_mailmap?(user,email) + if SITE_CONFIG['mailmap'].is_a? Hash and 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_user_from_email_address(email) SITE_CONFIG['email_dispatch'] == 'single_user' ? get_user_from_env_setting : get_user_from_mail_header(email) end diff --git a/config/routes.rb b/config/routes.rb index 4c3fccb3..5e2be629 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,8 @@ Tracksapp::Application.routes.draw do root :to => 'todos#index' + post 'mailgun/mime' => 'mailgun#mailgun' + post 'login' => 'login#login' get 'login' => 'login#login' get 'login/check_expiry' => 'login#check_expiry' diff --git a/config/site.yml.tmpl b/config/site.yml.tmpl index 1ee2094a..b024a885 100644 --- a/config/site.yml.tmpl +++ b/config/site.yml.tmpl @@ -58,7 +58,20 @@ open_signups: false # (see http://docs.cloudmailin.com/validating_the_sender) # cloudmailin: asdasd +# Mailgun api key - used to verify incoming HTTP requests from Mailgun.org +# mailgun_api_key: key-abcdef1234567890 # change this to reflect the email address of the admin that you want to show # on the signup page -admin_email: my.email@domain.com \ No newline at end of file +admin_email: my.email@domain.com + + +# Map of allowed incoming email addresses to real users +# Requires email_dispatch == 'to' +# This allows you to specify _who_ can send email Todos to your list +# The format is your incoming email (as per preferences page) for the key, and +# an array-list of acceptable senders for that account +#mailmap: +# 'user@preferences.from.value': +# - 'acceptable1@personal.org' +# - 'acceptable2@work.com' diff --git a/test/controllers/integrations_controller_test.rb b/test/controllers/integrations_controller_test.rb index d6412389..ba382a21 100644 --- a/test/controllers/integrations_controller_test.rb +++ b/test/controllers/integrations_controller_test.rb @@ -13,6 +13,7 @@ class IntegrationsControllerTest < ActionController::TestCase def test_cloudmailin_integration_success SITE_CONFIG['cloudmailin'] = "123456789" + SITE_CONFIG['email_dispatch'] = 'from' post :cloudmailin, { "html"=>"", "plain"=>"asdasd", diff --git a/test/controllers/mailgun_controller_test.rb b/test/controllers/mailgun_controller_test.rb new file mode 100644 index 00000000..b151afc9 --- /dev/null +++ b/test/controllers/mailgun_controller_test.rb @@ -0,0 +1,66 @@ +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') + +class MailgunControllerTest < ActionController::TestCase + + 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 + SITE_CONFIG['mailgun_api_key'] = "123456789" + SITE_CONFIG['email_dispatch'] = 'from' + + post :mailgun, { + "timestamp" => "1379539674", + "token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5", + "signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1", + "body-mime" => load_message('mailgun_message1.txt') + } + + assert_response :success + end + + def test_mailgun_creates_todo_with_mailmap + 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, { + "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 + + def test_mailgun_signature_fails + SITE_CONFIG['mailgun_api_key'] = "invalidkey" + SITE_CONFIG['email_dispatch'] = 'from' + + post :mailgun, { + "timestamp" => "1379539674", + "token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5", + "signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1", + "body-mime" => load_message('mailgun_message1.txt') + } + + assert_response 406 + end + +end diff --git a/test/fixtures/mailgun_message1.txt b/test/fixtures/mailgun_message1.txt new file mode 100644 index 00000000..4c5a695e --- /dev/null +++ b/test/fixtures/mailgun_message1.txt @@ -0,0 +1,27 @@ +Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000 +X-Envelope-From: <5555555555@tmomail.net> +Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC) +Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n ; Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig== +Mime-Version: 1.0 +X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Date: Wed, 18 Sep 2013 21:51:08 +0100 +Message-Id: +Subject: test1 +From: A User <5555555555@tmomail.net> +To: test@test.mailgun.org +Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\" +X-Mailgun-Incoming: Yes + +--bcaec520ee93c9b26104e6ae9838 +Content-Type: text/plain; charset=ISO-8859-1 + + + +--bcaec520ee93c9b26104e6ae9838 +Content-Type: text/html; charset=ISO-8859-1 + +

+ +--bcaec520ee93c9b26104e6ae9838-- diff --git a/test/fixtures/mailgun_message2.txt b/test/fixtures/mailgun_message2.txt new file mode 100644 index 00000000..4ea6c7ba --- /dev/null +++ b/test/fixtures/mailgun_message2.txt @@ -0,0 +1,27 @@ +Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000 +X-Envelope-From: +Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC) +Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n <5555555555@tmomail.net>; Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig== +Mime-Version: 1.0 +X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT) +Date: Wed, 18 Sep 2013 21:51:08 +0100 +Message-Id: +Subject: test +From: A User +To: 5555555555@tmomail.net +Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\" +X-Mailgun-Incoming: Yes + +--bcaec520ee93c9b26104e6ae9838 +Content-Type: text/plain; charset=ISO-8859-1 + + + +--bcaec520ee93c9b26104e6ae9838 +Content-Type: text/html; charset=ISO-8859-1 + +

+ +--bcaec520ee93c9b26104e6ae9838--