griddler-ses 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 96e0b0f54f1b00a46b12ae82cadb29dd52986091
4
+ data.tar.gz: d6c672a6b79f102995957f643421be3061e1de55
5
+ SHA512:
6
+ metadata.gz: 82b591b291899aac81572b1b2773e7f692baac826041a45e78f3cf1637fa7b91fcdb1bdae51c0f65120f0fa4f697c9252ce27b09e1095c03dda2bc886130ea74
7
+ data.tar.gz: f2e613ae216668b85d139b291090fafc26d513d18092dd80474ab8974ee87c2eaacc5611e361372d4158e7ae05a8450b91996795f507bb30cf2e3455511bb2c3
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in griddler-ses.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Coupa Software Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Griddler::Ses
2
+
3
+ This is a [Griddler](https://github.com/thoughtbot/griddler) adapter that allows you to parse email replies when used with Amazon SES.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add these lines to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'griddler'
12
+ gem 'griddler-ses'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+
20
+ ## Usage
21
+
22
+ 1. Setup Amazon SES to receive emails -- see http://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-setting-up.html
23
+
24
+ 2. From AWS SES -> Rule Sets, choose to "Create Receipt Rule" and choose to use SNS. Add an action with a new SNS topic that *ends in `_griddler`*, with a Base64 encoding.
25
+
26
+ 3. Go to AWS SNS (Simple Notification Service) -> Topics. Click on your `_griddler` topic. Add a subscription that points to your configured griddler endpoint (the default route is `/email_processor`). You're server must be already running to confirm the subscription request!
27
+
28
+ 4. In your griddler handler, be sure to handle/ignore empty reply emails (ie. check for `email.headers.empty?`) to account for the fact that some hooks are just SNS's subscription handling.
29
+
30
+ ## Contributing
31
+
32
+ Bug reports and pull requests are welcome on GitHub at https://github.com/85x14/griddler-ses.
33
+
34
+
35
+ ## License
36
+
37
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
38
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'griddler/ses/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "griddler-ses"
8
+ spec.version = Griddler::Ses::VERSION
9
+ spec.authors = ["Kent Mewhort @ Coupa"]
10
+ spec.email = ["kent.mewhort@coupa.com"]
11
+
12
+ spec.summary = %q{Griddler adapter for AWS SES (handle incoming email replies through SES)}
13
+ spec.homepage = "https://github.com/85x14/griddler-ses"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'griddler'
22
+ spec.add_runtime_dependency 'mail'
23
+ spec.add_runtime_dependency 'sns_endpoint'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.11"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ end
@@ -0,0 +1,142 @@
1
+ require 'mail'
2
+ require 'sns_endpoint'
3
+ require 'net/http'
4
+
5
+ module Griddler
6
+ module Ses
7
+ class Adapter
8
+ attr_reader :params, :raw_request
9
+
10
+ def initialize(params, raw_request)
11
+ @params = params
12
+ @raw_request = raw_request
13
+ end
14
+
15
+ def self.normalize_params(params)
16
+ adapter = new(params, self.raw_request)
17
+ adapter.normalize_params
18
+ end
19
+
20
+ def normalize_params
21
+ # use sns_endpoint to parse and validate the sns message
22
+ sns_msg = SnsEndpoint::AWS::SNS::Message.new sns_json
23
+ raise "Invalid SNS message" unless sns_msg.authentic? && sns_msg.topic_arn.end_with?('griddler')
24
+
25
+ case sns_msg.type
26
+ when :SubscriptionConfirmation
27
+ confirm_sns_subscription_request
28
+ # this is not an actual email reply (and griddler has no way to bail at this point), so return empty parameters
29
+ {}
30
+ when :Notification
31
+ ensure_valid_notification_type!
32
+ params.merge(
33
+ to: recipients,
34
+ from: sender,
35
+ cc: cc,
36
+ subject: subject,
37
+ text: text_part,
38
+ html: html_part,
39
+ headers: raw_headers,
40
+ attachments: attachment_files
41
+ )
42
+ else
43
+ raise "Invalid SNS message type"
44
+ end
45
+ end
46
+
47
+ private
48
+ def sns_json
49
+ @sns_json ||= JSON.parse(raw_request.raw_post)
50
+ end
51
+
52
+ def email_json
53
+ @email_json ||= JSON.parse(sns_json['Message'])
54
+ end
55
+
56
+ def notification_type
57
+ email_json['notificationType']
58
+ end
59
+
60
+ def recipients
61
+ email_json['receipt']['recipients']
62
+ end
63
+
64
+ def sender
65
+ email_json['mail']['commonHeaders']['from'].first
66
+ end
67
+
68
+ def cc
69
+ email_json['mail']['commonHeaders']['cc']
70
+ end
71
+
72
+ def subject
73
+ email_json['mail']['commonHeaders']['subject']
74
+ end
75
+
76
+ def header_array
77
+ email_json['mail']['headers']
78
+ end
79
+
80
+ def message
81
+ @message ||= Mail.read_from_string(Base64.decode64(email_json['content']))
82
+ end
83
+
84
+ def multipart?
85
+ message.parts.count > 0
86
+ end
87
+
88
+ def text_part
89
+ multipart? ? message.text_part.body.to_s : message.body.to_s
90
+ end
91
+
92
+ def html_part
93
+ multipart? ? message.html_part.body.to_s : nil
94
+ end
95
+
96
+ def raw_headers
97
+ # SNS gives us an array of hashes with name value, which we need to convert back to raw headers;
98
+ # based on griddler-sparkpost (https://github.com/PrestoDoctor/griddler-sparkpost, MIT license)
99
+ header_array.inject([]) { |raw_headers, sns_hash|
100
+ raw_headers.push("#{sns_hash['name']}: #{sns_hash['value']}")
101
+ }.join("\r\n")
102
+ end
103
+
104
+ def attachment_files
105
+ # also based on griddler-sparkpost (https://github.com/PrestoDoctor/griddler-sparkpost, MIT license);
106
+ # AWS doesn't presently support sending the attachments from the message through SNS, but ready if they do!
107
+ message.attachments.map do |attachment|
108
+ ActionDispatch::Http::UploadedFile.new({
109
+ type: attachment.mime_type,
110
+ filename: attachment.filename,
111
+ tempfile: tempfile_for_attachment(attachment)
112
+ })
113
+ end
114
+ end
115
+
116
+ def tempfile_for_attachment(attachment)
117
+ filename = attachment.filename.gsub(/\/|\\/, '_')
118
+ tempfile = Tempfile.new(filename, Dir::tmpdir, encoding: 'ascii-8bit')
119
+ content = attachment.body.decoded
120
+ tempfile.write(content)
121
+ tempfile.rewind
122
+ tempfile
123
+ end
124
+
125
+ def ensure_valid_notification_type!
126
+ raise "Invalid SNS notification type (\"#{notification_type}\", expecting Received" unless notification_type == 'Received'
127
+ end
128
+
129
+ def confirm_sns_subscription_request
130
+ confirmation_endpoint = URI.parse(sns_json['SubscribeURL'])
131
+ Net::HTTP.get confirmation_endpoint
132
+ end
133
+
134
+ def self.raw_request
135
+ # TODO: this is an ugly hack using introspection to get the request from the calling controller; should update Griddler
136
+ # to provide the full request context
137
+ raw_request = binding.of_caller(2).eval('request')
138
+ end
139
+ end
140
+ end
141
+ end
142
+
@@ -0,0 +1,5 @@
1
+ module Griddler
2
+ module Ses
3
+ VERSION = "1.0"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'griddler'
2
+ require 'griddler/ses/version'
3
+ require 'griddler/ses/adapter'
4
+
5
+ module Griddler
6
+ module Ses
7
+ end
8
+ end
9
+
10
+ Griddler.adapter_registry.register(:ses, Griddler::Ses::Adapter)
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: griddler-ses
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Kent Mewhort @ Coupa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: griddler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mail
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sns_endpoint
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description:
98
+ email:
99
+ - kent.mewhort@coupa.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - griddler-ses.gemspec
111
+ - lib/griddler/ses.rb
112
+ - lib/griddler/ses/adapter.rb
113
+ - lib/griddler/ses/version.rb
114
+ homepage: https://github.com/85x14/griddler-ses
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.4.6
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Griddler adapter for AWS SES (handle incoming email replies through SES)
138
+ test_files: []