mirror of https://github.com/rails/rails
Extract ActionMailbox::PostfixRelayer
This commit is contained in:
parent
2e4df7cea3
commit
148110e70c
24
Gemfile.lock
24
Gemfile.lock
|
@ -66,7 +66,6 @@ PATH
|
|||
remote: .
|
||||
specs:
|
||||
actionmailbox (0.1.0)
|
||||
http (>= 4.0.0)
|
||||
rails (>= 5.2.0)
|
||||
|
||||
GEM
|
||||
|
@ -88,21 +87,13 @@ GEM
|
|||
builder (3.2.3)
|
||||
byebug (10.0.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.4)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
erubi (1.7.1)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
http (4.0.0)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 2.0)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
hashdiff (0.3.7)
|
||||
i18n (1.1.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.4.0)
|
||||
|
@ -131,6 +122,7 @@ GEM
|
|||
rails-html-sanitizer (1.0.4)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rake (12.3.1)
|
||||
safe_yaml (1.0.4)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
|
@ -143,9 +135,10 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
webmock (3.4.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
websocket-driver (0.7.0)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
|
@ -160,6 +153,7 @@ DEPENDENCIES
|
|||
byebug
|
||||
rails!
|
||||
sqlite3
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.6
|
||||
|
|
|
@ -16,11 +16,11 @@ Gem::Specification.new do |s|
|
|||
s.required_ruby_version = ">= 2.5.0"
|
||||
|
||||
s.add_dependency "rails", ">= 5.2.0"
|
||||
s.add_dependency "http", ">= 4.0.0"
|
||||
|
||||
s.add_development_dependency "bundler", "~> 1.15"
|
||||
s.add_development_dependency "sqlite3"
|
||||
s.add_development_dependency "byebug"
|
||||
s.add_development_dependency "webmock"
|
||||
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.test_files = `git ls-files -- test/*`.split("\n")
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "net/http"
|
||||
require "uri"
|
||||
require "openssl"
|
||||
|
||||
module ActionMailbox
|
||||
class PostfixRelayer
|
||||
class Result < Struct.new(:output)
|
||||
def success?
|
||||
!failure?
|
||||
end
|
||||
|
||||
def failure?
|
||||
output.match?(/\A[45]\.\d\.\d /)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :uri, :username, :password, :user_agent
|
||||
|
||||
def initialize(url:, username: "actionmailbox", password:, user_agent: nil)
|
||||
@uri, @username, @password, @user_agent = URI(url), username, password, user_agent || "Postfix"
|
||||
end
|
||||
|
||||
def relay(source)
|
||||
case response = post(source)
|
||||
when Net::HTTPSuccess
|
||||
Result.new "2.0.0 Successfully relayed message to Postfix ingress"
|
||||
when Net::HTTPUnauthorized
|
||||
Result.new "4.7.0 Invalid credentials for Postfix ingress"
|
||||
else
|
||||
Result.new "4.0.0 HTTP #{response.code}"
|
||||
end
|
||||
rescue IOError, SocketError, SystemCallError => error
|
||||
Result.new "4.4.2 Network error relaying to Postfix ingress: #{error.message}"
|
||||
rescue Timeout::Error
|
||||
Result.new "4.4.2 Timed out relaying to Postfix ingress"
|
||||
rescue => error
|
||||
Result.new "4.0.0 Error relaying to Postfix ingress: #{error.message}"
|
||||
end
|
||||
|
||||
private
|
||||
def post(source)
|
||||
client.post uri.path, source,
|
||||
"Content-Type" => "message/rfc822",
|
||||
"User-Agent" => user_agent,
|
||||
"Authorization" => "Basic #{Base64.strict_encode64(username + ":" + password)}"
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= Net::HTTP.new(uri.host, uri.port).tap do |connection|
|
||||
connection.use_ssl = uri.scheme == "https"
|
||||
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
|
||||
connection.open_timeout = 1
|
||||
connection.read_timeout = 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,45 +2,24 @@
|
|||
|
||||
namespace :action_mailbox do
|
||||
namespace :ingress do
|
||||
desc "Pipe an inbound email from STDIN to the Postfix ingress at the given URL"
|
||||
desc "Pipe an inbound email from STDIN to the Postfix ingress (URL and INGRESS_PASSWORD required)"
|
||||
task :postfix do
|
||||
require "active_support"
|
||||
require "active_support/core_ext/object/blank"
|
||||
require "http"
|
||||
require "action_mailbox/ingresses/postfix/relayer"
|
||||
|
||||
url, password = ENV.values_at("URL", "INGRESS_PASSWORD")
|
||||
url, password, user_agent = ENV.values_at("URL", "INGRESS_PASSWORD", "USER_AGENT")
|
||||
|
||||
if url.blank? || password.blank?
|
||||
puts "4.3.5 URL and INGRESS_PASSWORD are required"
|
||||
echo "4.3.5 URL and INGRESS_PASSWORD are required"
|
||||
exit 1
|
||||
end
|
||||
|
||||
begin
|
||||
response = HTTP.basic_auth(user: "actionmailbox", pass: password)
|
||||
.timeout(connect: 1, write: 10, read: 10)
|
||||
.post(url, body: STDIN.read,
|
||||
headers: { "Content-Type" => "message/rfc822", "User-Agent" => ENV.fetch("USER_AGENT", "Postfix") })
|
||||
|
||||
case
|
||||
when response.status.success?
|
||||
puts "2.0.0 HTTP #{response.status}"
|
||||
when response.status.unauthorized?
|
||||
puts "4.7.0 HTTP #{response.status}"
|
||||
exit 1
|
||||
when response.status.unsupported_media_type?
|
||||
puts "5.6.1 HTTP #{response.status}"
|
||||
exit 1
|
||||
else
|
||||
puts "4.0.0 HTTP #{response.status}"
|
||||
exit 1
|
||||
ActionMailbox::PostfixRelayer.new(url: url, password: password, user_agent: user_agent)
|
||||
.relay(STDIN.read).tap do |result|
|
||||
echo result.output
|
||||
exit result.success? ? 0 : 1
|
||||
end
|
||||
rescue HTTP::ConnectionError => error
|
||||
puts "4.4.2 Error connecting to the Postfix ingress: #{error.message}"
|
||||
exit 1
|
||||
rescue HTTP::TimeoutError
|
||||
puts "4.4.7 Timed out piping to the Postfix ingress"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/mi
|
|||
require "rails/test_help"
|
||||
|
||||
require "byebug"
|
||||
require "webmock/minitest"
|
||||
|
||||
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
require_relative '../test_helper'
|
||||
|
||||
require 'action_mailbox/postfix_relayer'
|
||||
|
||||
module ActionMailbox
|
||||
class PostfixRelayerTest < ActiveSupport::TestCase
|
||||
URL = "https://example.com/rails/action_mailbox/postfix/inbound_emails"
|
||||
INGRESS_PASSWORD = "secret"
|
||||
|
||||
setup do
|
||||
@relayer = ActionMailbox::PostfixRelayer.new(url: URL, password: INGRESS_PASSWORD)
|
||||
end
|
||||
|
||||
test "successfully relaying an email" do
|
||||
stub_request(:post, URL).to_return status: 204
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "2.0.0 Successfully relayed message to Postfix ingress", result.output
|
||||
assert result.success?
|
||||
assert_not result.failure?
|
||||
|
||||
assert_requested :post, URL, body: file_fixture("welcome.eml").read,
|
||||
basic_auth: [ "actionmailbox", INGRESS_PASSWORD ], headers: { "Content-Type" => "message/rfc822", "User-Agent" => "Postfix" }
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying with invalid credentials" do
|
||||
stub_request(:post, URL).to_return status: 401
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.7.0 Invalid credentials for Postfix ingress", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying due to an unspecified server error" do
|
||||
stub_request(:post, URL).to_return status: 500
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.0.0 HTTP 500", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying due to a gateway timeout" do
|
||||
stub_request(:post, URL).to_return status: 504
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.0.0 HTTP 504", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying due to ECONNRESET" do
|
||||
stub_request(:post, URL).to_raise Errno::ECONNRESET.new
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.4.2 Network error relaying to Postfix ingress: Connection reset by peer", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying due to connection failure" do
|
||||
stub_request(:post, URL).to_raise SocketError.new("Failed to open TCP connection to example.com:443")
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.4.2 Network error relaying to Postfix ingress: Failed to open TCP connection to example.com:443", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
|
||||
test "unsuccessfully relaying due to an unhandled exception" do
|
||||
stub_request(:post, URL).to_raise StandardError.new("Something went wrong")
|
||||
|
||||
result = @relayer.relay(file_fixture("welcome.eml").read)
|
||||
assert_equal "4.0.0 Error relaying to Postfix ingress: Something went wrong", result.output
|
||||
assert_not result.success?
|
||||
assert result.failure?
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue