diff options
author | George Claghorn <george@basecamp.com> | 2018-10-06 22:02:08 -0400 |
---|---|---|
committer | George Claghorn <george@basecamp.com> | 2018-10-11 12:51:13 -0400 |
commit | 6b7eac5c51cbef4acd1ab7f48884e7b614f71678 (patch) | |
tree | 8eb6b0ebee4ad6073f6cb48b3effc60d4262d2f2 /app | |
parent | 47445511862a4c9979fb46889011edf585391091 (diff) | |
download | rails-6b7eac5c51cbef4acd1ab7f48884e7b614f71678.tar.gz rails-6b7eac5c51cbef4acd1ab7f48884e7b614f71678.tar.bz2 rails-6b7eac5c51cbef4acd1ab7f48884e7b614f71678.zip |
Accept inbound emails from a variety of ingresses
Diffstat (limited to 'app')
7 files changed, 133 insertions, 18 deletions
diff --git a/app/controllers/action_mailbox/base_controller.rb b/app/controllers/action_mailbox/base_controller.rb new file mode 100644 index 0000000000..680c6a9615 --- /dev/null +++ b/app/controllers/action_mailbox/base_controller.rb @@ -0,0 +1,11 @@ +class ActionMailbox::BaseController < ActionController::Base + skip_forgery_protection + + private + def authenticate + authenticate_or_request_with_http_basic("Action Mailbox") do |given_username, given_password| + ActiveSupport::SecurityUtils.secure_compare(given_username, username) && + ActiveSupport::SecurityUtils.secure_compare(given_password, password) + end + end +end diff --git a/app/controllers/action_mailbox/inbound_emails_controller.rb b/app/controllers/action_mailbox/inbound_emails_controller.rb deleted file mode 100644 index ec9bd6f229..0000000000 --- a/app/controllers/action_mailbox/inbound_emails_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -# TODO: Add access protection using basic auth with verified tokens. Maybe coming from credentials by default? -# TODO: Spam/malware catching? -# TODO: Specific bounces for SMTP good citizenship: 200/404/400 -class ActionMailbox::InboundEmailsController < ActionController::Base - skip_forgery_protection - before_action :require_rfc822_message, only: :create - - def create - ActionMailbox::InboundEmail.create_and_extract_message_id!(params[:message]) - head :created - end - - private - def require_rfc822_message - head :unsupported_media_type unless params.require(:message).content_type == 'message/rfc822' - end -end diff --git a/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb new file mode 100644 index 0000000000..7412928b56 --- /dev/null +++ b/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb @@ -0,0 +1,26 @@ +class ActionMailbox::Ingresses::Amazon::InboundEmailsController < ActionMailbox::BaseController + before_action :ensure_verified + + # TODO: Lazy-load the AWS SDK + require "aws-sdk-sns/message_verifier" + cattr_accessor :verifier, default: Aws::SNS::MessageVerifier.new + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email + head :no_content + end + + private + def raw_email + StringIO.new params.require(:content) + end + + + def ensure_verified + head :unauthorized unless verified? + end + + def verified? + verifier.authentic?(request.body) + end +end diff --git a/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb new file mode 100644 index 0000000000..4d194a3e00 --- /dev/null +++ b/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb @@ -0,0 +1,61 @@ +class ActionMailbox::Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController + before_action :ensure_authenticated + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email + head :ok + end + + private + def raw_email + StringIO.new params.require("body-mime") + end + + + def ensure_authenticated + head :unauthorized unless authenticated? + end + + def authenticated? + Authenticator.new(authentication_params).authenticated? + rescue ArgumentError + false + end + + def authentication_params + params.permit(:timestamp, :token, :signature).to_h.symbolize_keys + end + + + class Authenticator + cattr_accessor :key + + attr_reader :timestamp, :token, :signature + + def initialize(timestamp:, token:, signature:) + @timestamp, @token, @signature = timestamp, token, signature + end + + def authenticated? + signed? && recent? + end + + private + def signed? + ActiveSupport::SecurityUtils.secure_compare signature, expected_signature + end + + # Allow for 10 minutes of drift between Mailgun time and local server time. + def recent? + time >= 10.minutes.ago + end + + def expected_signature + OpenSSL::HMAC.hexdigest OpenSSL::Digest::SHA256.new, key, "#{timestamp}#{token}" + end + + def time + Time.at Integer(timestamp) + end + end +end diff --git a/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb new file mode 100644 index 0000000000..2631302606 --- /dev/null +++ b/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb @@ -0,0 +1,11 @@ +class ActionMailbox::Ingresses::Postfix::InboundEmailsController < ActionMailbox::BaseController + cattr_accessor :username, default: "actionmailbox" + cattr_accessor :password + + before_action :authenticate + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! params.require(:message) + head :no_content + end +end diff --git a/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb new file mode 100644 index 0000000000..0b9e2e1866 --- /dev/null +++ b/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb @@ -0,0 +1,16 @@ +class ActionMailbox::Ingresses::Sendgrid::InboundEmailsController < ActionMailbox::BaseController + cattr_accessor :username, default: "actionmailbox" + cattr_accessor :password + + before_action :authenticate + + def create + ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email + head :no_content + end + + private + def raw_email + StringIO.new params.require(:email) + end +end diff --git a/app/models/action_mailbox/inbound_email/message_id.rb b/app/models/action_mailbox/inbound_email/message_id.rb index 590dbfc4d7..601c5f1a7e 100644 --- a/app/models/action_mailbox/inbound_email/message_id.rb +++ b/app/models/action_mailbox/inbound_email/message_id.rb @@ -7,7 +7,14 @@ module ActionMailbox::InboundEmail::MessageId module ClassMethods def create_and_extract_message_id!(raw_email, **options) - create! raw_email: raw_email, message_id: extract_message_id(raw_email), **options + create! message_id: extract_message_id(raw_email), **options do |inbound_email| + case raw_email + when ActionDispatch::Http::UploadedFile + inbound_email.raw_email.attach raw_email + else + inbound_email.raw_email.attach io: raw_email.tap(&:rewind), filename: "message.eml", content_type: "message/rfc822" + end + end end private |