Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encoding::CompatibilityError fix #15

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in griddler-ses.gemspec
gemspec
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Griddler::Ses
# Griddler::AmazonSES

This is a [Griddler](https://github.com/thoughtbot/griddler) adapter that allows you to parse email replies when used with Amazon SES.

Expand All @@ -9,7 +9,7 @@ Add these lines to your application's Gemfile:

```ruby
gem 'griddler'
gem 'griddler-ses'
gem 'griddler-amazon_ses'
```

And then execute:
Expand All @@ -29,7 +29,7 @@ And then execute:

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/85x14/griddler-ses.
Bug reports and pull requests are welcome on GitHub at https://github.com/ccallebs/griddler-amazon_ses.


## License
Expand Down
14 changes: 7 additions & 7 deletions griddler-ses.gemspec → griddler-amazon_ses.gemspec
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'griddler/ses/version'
require 'griddler/amazon_ses/version'

Gem::Specification.new do |spec|
spec.name = "griddler-ses"
spec.version = Griddler::Ses::VERSION
spec.authors = ["Kent Mewhort @ Coupa"]
spec.email = ["[email protected]"]
spec.name = "griddler-amazon_ses"
spec.version = Griddler::AmazonSES::VERSION
spec.authors = ["Chuck Callebs", "Kent Mewhort @ Coupa"]
spec.email = ["[email protected]", "[email protected]"]

spec.summary = %q{Griddler adapter for AWS SES (handle incoming email replies through SES)}
spec.homepage = "https://github.com/85x14/griddler-ses"
spec.homepage = "https://github.com/ccallebs/griddler-amazon_ses"
spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
Expand All @@ -20,7 +20,7 @@ Gem::Specification.new do |spec|

spec.add_runtime_dependency 'griddler'
spec.add_runtime_dependency 'mail'
spec.add_runtime_dependency 'sns_endpoint'
spec.add_runtime_dependency 'httparty'

spec.add_development_dependency "bundler", "~> 1.11"
spec.add_development_dependency "rake", "~> 10.0"
Expand Down
156 changes: 156 additions & 0 deletions lib/aws/sns_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.


# Forked from https://github.com/elibri/sns_endpoint

require 'base64'
require 'json'
require 'openssl'
require 'httparty'

module AWS
class MessageWasNotAuthenticError < StandardError
end

class SnsMessage
attr_accessor :origin, :raw

def initialize sns
if sns.is_a? String
@raw = parse_from sns
else
@raw = sns
end
@origin = :sns
end

def [] key
@raw[key]
end

def authentic?
begin
decoded_from_base64 = decode signature
public_key = get_public_key_from signing_cert_url
public_key.verify OpenSSL::Digest::SHA1.new, decoded_from_base64, canonical_string
rescue MessageWasNotAuthenticError
false
end
end

def type
case when @raw['Type'] =~ /SubscriptionConfirmation/i
then :SubscriptionConfirmation
when @raw['Type'] =~ /Notification/i
then :Notification
when @raw['Type'] =~ /UnsubscribeConfirmation/i
then :UnsubscribeConfirmation
else
:unknown
end
end

def message_id
@raw['MessageId']
end

def topic_arn
@raw['TopicArn']
end

def subject
@raw['Subject']
end

def message
@raw['Message']
end

def timestamp
@raw['Timestamp']
end

def signature
@raw['Signature']
end

def signature_version
@raw['SignatureVersion']
end

def signing_cert_url
@raw['SigningCertURL']
end

def unsubscribe_url
@raw['UnsubscribeURL']
end

def subscribe_url
@raw['SubscribeURL']
end

def token
@raw['Token']
end

def parse_from json
JSON.parse json
end

protected
def decode raw
Base64.decode64 raw
end

def get_public_key_from(x509_pem_url)
cert_pem = download x509_pem_url
x509 = OpenSSL::X509::Certificate.new(cert_pem)
OpenSSL::PKey::RSA.new(x509.public_key)
end

def canonical_string
if type == :SubscriptionConfirmation
text = "Message\n#{message}\n"
text += "MessageId\n#{message_id}\n"
text += "SubscribeURL\n#{subscribe_url}\n"
text += "Timestamp\n#{timestamp}\n"
text += "Token\n#{token}\n"
text += "TopicArn\n#{topic_arn}\n"
text += "Type\n#{type}\n"
else
text = "Message\n#{message}\n"
text += "MessageId\n#{message_id}\n"
text += "Subject\n#{subject}\n" unless subject.nil? or subject.empty?
text += "Timestamp\n#{timestamp}\n"
text += "TopicArn\n#{topic_arn}\n"
text += "Type\n#{type}\n"
end
text
end

def download url
raise MessageWasNotAuthenticError, "cert is not hosted at AWS URL (https): #{url}" unless url =~ /^https.*amazonaws\.com\/.*$/i
tries = 0
begin
response = HTTParty.get url
response.body
rescue => msg
tries += 1
retry if tries <= 3
raise StandardError, "SNS signing cert could not be retrieved after #{tries} tries.\n#{msg}"
end
end
end
end
13 changes: 13 additions & 0 deletions lib/griddler/amazon_ses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'aws/sns_message'
require 'griddler'
require 'griddler/amazon_ses/version'
require 'griddler/amazon_ses/adapter'
require 'griddler/amazon_ses/middleware'
require 'griddler/amazon_ses/railtie'

module Griddler
module AmazonSES
end
end

Griddler.adapter_registry.register(:amazon_ses, Griddler::AmazonSES::Adapter)
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
require 'mail'
require 'sns_endpoint'
require 'net/http'

module Griddler
module Ses
module AmazonSES
class Adapter
attr_reader :sns_json

Expand All @@ -17,8 +16,7 @@ def self.normalize_params(params)
end

def normalize_params
# use sns_endpoint to parse and validate the sns message
sns_msg = SnsEndpoint::AWS::SNS::Message.new sns_json
sns_msg = AWS::SnsMessage.new sns_json
raise "Invalid SNS message" unless sns_msg.authentic? && sns_msg.topic_arn.end_with?('griddler')

case sns_msg.type
Expand Down Expand Up @@ -86,11 +84,15 @@ def multipart?
end

def text_part
multipart? ? message.text_part.body.to_s : message.body.to_s
force_body_to_utf_8_string(multipart? ? message.text_part.body : message.body)
end

def html_part
multipart? ? message.html_part.body.to_s : nil
multipart? ? force_body_to_utf_8_string(message.html_part.body) : nil
end

def force_body_to_utf_8_string(message_body)
message_body.to_s.force_encoding(Encoding::UTF_8)
end

def raw_headers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Griddler
module Ses
module AmazonSES
class Middleware
def initialize(app)
@app = app
Expand All @@ -22,7 +22,8 @@ def griddler_path
end

def is_griddler_request?(request)
request['REQUEST_PATH'] == griddler_path
# Fix for servers that do not include 'request_path' in headers
request['REQUEST_PATH'] == griddler_path || request['REQUEST_URI'] == griddler_path
end

def is_aws_sns_request?(request)
Expand Down
17 changes: 17 additions & 0 deletions lib/griddler/amazon_ses/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'rails/version'

module Griddler
module AmazonSES
class Railtie < Rails::Railtie
if Rails::VERSION::MAJOR < 5
middleware = ActionDispatch::ParamsParser
else
middleware = Rack::Head
end

initializer "griddler_ses.configure_rails_initialization" do |app|
Rails.application.middleware.insert_before middleware, Griddler::AmazonSES::Middleware
end
end
end
end
5 changes: 5 additions & 0 deletions lib/griddler/amazon_ses/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Griddler
module AmazonSES
VERSION = "2.0.1"
end
end
12 changes: 0 additions & 12 deletions lib/griddler/ses.rb

This file was deleted.

9 changes: 0 additions & 9 deletions lib/griddler/ses/railtie.rb

This file was deleted.

5 changes: 0 additions & 5 deletions lib/griddler/ses/version.rb

This file was deleted.

22 changes: 13 additions & 9 deletions spec/griddler/ses_spec.rb → spec/griddler/amazon_ses_spec.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
require 'spec_helper'

describe Griddler::Ses::Adapter do
describe Griddler::AmazonSES::Adapter do
before do
# mock the hash check on the notification, as we've zero'd the numbers
allow_any_instance_of(SnsEndpoint::AWS::SNS::Message).to receive(:authentic?).and_return(true)
allow_any_instance_of(AWS::SnsMessage).to receive(:authentic?).and_return(true)
end

it 'registers itself with griddler' do
Griddler.adapter_registry[:ses].should eq Griddler::Ses::Adapter
expect(Griddler.adapter_registry[:amazon_ses]).to eq Griddler::AmazonSES::Adapter
end

describe "Griddler shared examples" do
Expand All @@ -16,27 +16,31 @@
sns_message[:mail][:commonHeaders][:cc] = ['[email protected]']
sns_message[:mail][:commonHeaders][:from] = ['There <[email protected]>']

allow_any_instance_of(Griddler::Ses::Adapter).to receive(:sns_json).and_return(default_params)
allow_any_instance_of(Griddler::AmazonSES::Adapter).to receive(:sns_json).and_return(default_params)
end

it_behaves_like 'Griddler adapter', :ses, {}
it_behaves_like 'Griddler adapter', :amazon_ses, {}
end

describe '.normalize_params' do
it 'parses out the "to" addresses, returning an array' do
expect(Griddler::Ses::Adapter.normalize_params(default_params)[:to]).to eq ['"Mr Fugushima at Fugu, Inc" <[email protected]>', 'Foo bar <[email protected]>']
expect(Griddler::AmazonSES::Adapter.normalize_params(default_params)[:to]).to eq ['"Mr Fugushima at Fugu, Inc" <[email protected]>', 'Foo bar <[email protected]>']
end

it 'parses out the "from" address, returning a string' do
expect(Griddler::Ses::Adapter.normalize_params(default_params)[:from]).to eq "Test There <[email protected]>"
expect(Griddler::AmazonSES::Adapter.normalize_params(default_params)[:from]).to eq "Test There <[email protected]>"
end

it 'parses out the "subject", returning a string' do
expect(Griddler::Ses::Adapter.normalize_params(default_params)[:subject]).to eq "Test"
expect(Griddler::AmazonSES::Adapter.normalize_params(default_params)[:subject]).to eq "Test"
end

it 'parses out the text' do
expect(Griddler::Ses::Adapter.normalize_params(default_params)[:text]).to eq "Hi\n"
expect(Griddler::AmazonSES::Adapter.normalize_params(default_params)[:text]).to eq "Hi\n"
end

it 'should return the text body in UTF-8' do
expect(Griddler::AmazonSES::Adapter.normalize_params(default_params)[:text].encoding.to_s).to eq "UTF-8"
end
end

Expand Down
Loading