mandrill-rails 0.0.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +5 -3
- data/CHANGELOG +5 -2
- data/Guardfile +1 -1
- data/README.rdoc +44 -3
- data/lib/mandrill-rails.rb +2 -2
- data/lib/mandrill-rails/version.rb +1 -1
- data/lib/mandrill-rails/web_hook_processor.rb +46 -6
- data/lib/mandrill/web_hook/processor.rb +37 -4
- data/spec/fixtures/webhook_examples/click_with_signature.json +21 -0
- data/spec/mandrill-rails/web_hook_processor_spec.rb +112 -6
- data/spec/mandrill/web_hook/processor_spec.rb +55 -2
- data/spec/support/fixtures_helper.rb +5 -1
- metadata +5 -4
- data/.rspec +0 -1
data/.travis.yml
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
# see http://travis-ci.org/evendis/mandrill-rails
|
3
3
|
language: ruby
|
4
4
|
rvm:
|
5
|
-
|
6
|
-
|
5
|
+
# removing 1.8 and 1.9.2 tests because we don't really care anymore and activesupport 4 won't like them
|
6
|
+
#- 1.8.7
|
7
|
+
#- 1.9.2
|
8
|
+
#- jruby-18mode
|
7
9
|
- 1.9.3
|
10
|
+
- 2.0.0
|
8
11
|
# - rbx-18mode # removing for now; travis is having chronic intermittent issues building this properly at the moment
|
9
12
|
- rbx-19mode
|
10
|
-
- jruby-18mode
|
11
13
|
- jruby-19mode
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
-
0.0
|
2
|
-
|
1
|
+
== 1.0.0
|
2
|
+
* added support for Mandrill webhook authentication (thanks to @zshannon for the contribution)
|
3
|
+
* stepped to 1.0.0 (applying Semantic Versioning principles; see http://semver.org/)
|
4
|
+
|
5
|
+
== 0.0.4
|
3
6
|
* removed manadrill gem as a dependency (thanks to @ssaunier for highlighting this)
|
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# A sample Guardfile
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
3
3
|
|
4
|
-
guard 'rspec', :version => 2 do
|
4
|
+
guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
|
5
5
|
watch(%r{^spec/.+_spec\.rb$})
|
6
6
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
7
|
watch('spec/spec_helper.rb') { "spec" }
|
data/README.rdoc
CHANGED
@@ -11,9 +11,9 @@ FYI, {Mandrill}[http://mandrill.com/] is the transactional email service by the
|
|
11
11
|
|
12
12
|
== Requirements and Known Limitations
|
13
13
|
|
14
|
-
* Tested with MRI 1.
|
15
|
-
*
|
16
|
-
* Requires Rails >= 3.0.3 (including 3.1
|
14
|
+
* Tested with MRI 1.9.3, 2.0.0, Rubinius (1.9 mode), JRuby (1.9 mode).
|
15
|
+
* MRI 1.8.7, 1.9.2, JRuby 1.8 mode and Rubinius 1.8 mode do work but you must force activesupport < 4.
|
16
|
+
* Requires Rails >= 3.0.3 (including 3.1, 3.2 and 4).
|
17
17
|
|
18
18
|
Food for thought (upcoming features maybe)..
|
19
19
|
* some generators may be handy to avoid the manual coding to wire up web hooks
|
@@ -112,6 +112,46 @@ your handler individually.
|
|
112
112
|
|
113
113
|
And if you don't care to handle a specific payload type - then just don't implement the associated handler.
|
114
114
|
|
115
|
+
=== How can I authenticate Mandrill Webhooks?
|
116
|
+
|
117
|
+
Mandrill now supports {webhook authentication}[http://help.mandrill.com/entries/23704122-Authenticating-webhook-requests] which can help prevent unauthorised posting to your webhook handlers. You can lookup and reset your API keys on the
|
118
|
+
{Mandrill WebHook settings}[https://mandrillapp.com/settings/webhooks] page.
|
119
|
+
|
120
|
+
If you do not configure your webhook API key, then the handlers will continue to work fine - they just won't be authenticated.
|
121
|
+
|
122
|
+
To enable authentication, use the <tt>authenticate_with_mandrill_keys!</tt> method to set your API key. It is recommended you pull
|
123
|
+
your API keys from environment settings, or use some other means to avoid committing the API keys in your source code.
|
124
|
+
|
125
|
+
For example, to handle inbound email:
|
126
|
+
|
127
|
+
class InboxController < ApplicationController
|
128
|
+
include Mandrill::Rails::WebHookProcessor
|
129
|
+
authenticate_with_mandrill_keys! 'YOUR_MANDRILL_WEBHOOK_KEY'
|
130
|
+
|
131
|
+
def handle_inbound(event_payload)
|
132
|
+
# .. handler methods will only be called if authentication has succeeded.
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
=== How can I authenticate multiple Mandrill Webhooks in the same controller?
|
138
|
+
|
139
|
+
Sometimes you may have more than one WebHook sending requests to a single controller, for example if you have one handling 'click' events, and another sending inbound email. Mandrill assigns separate API keys to each of these.
|
140
|
+
|
141
|
+
In this case, just add all the valid API keys you will allow with <tt>authenticate_with_mandrill_keys!</tt>, for example:
|
142
|
+
|
143
|
+
class InboxController < ApplicationController
|
144
|
+
include Mandrill::Rails::WebHookProcessor
|
145
|
+
authenticate_with_mandrill_keys! 'MANDRILL_CLICK_WEBHOOK_KEY', 'MANDRILL_INBOUND_WEBHOOK_KEY', 'ANOTHER_WEBHOOK_KEY'
|
146
|
+
|
147
|
+
def handle_inbound(event_payload)
|
148
|
+
end
|
149
|
+
|
150
|
+
def handle_click(event_payload)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
115
155
|
=== How do I pull apart the event_payload?
|
116
156
|
|
117
157
|
The <tt>event_payload</tt> object passed to our handler represents a single event and is packaged
|
@@ -224,6 +264,7 @@ and {MailChimp}[https://rubygems.org/search?utf8=%E2%9C%93&query=mailchimp] gems
|
|
224
264
|
If you need direct API integration in addition to Mandrill::Rails features,
|
225
265
|
you can choose to add whichever best meets your needs and use as normal.
|
226
266
|
|
267
|
+
|
227
268
|
== Contributing to Mandrill::Rails
|
228
269
|
|
229
270
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
data/lib/mandrill-rails.rb
CHANGED
@@ -35,20 +35,60 @@ module Mandrill::Rails::WebHookProcessor
|
|
35
35
|
|
36
36
|
included do
|
37
37
|
skip_before_filter :verify_authenticity_token
|
38
|
+
before_filter :authenticate_mandrill_request!, :only => [:create]
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
+
module ClassMethods
|
42
|
+
# Gets/sets the current Mandrill WebHook Authentication key(s).
|
43
|
+
# Returns the current WebHook key(s) as an Array if called with no parameters.
|
44
|
+
# If called with parameters, add the params to the WebHook key array.
|
45
|
+
# If called with nil as the parameters, clears the WebHook key array.
|
46
|
+
def authenticate_with_mandrill_keys!(*keys)
|
47
|
+
@mandrill_webhook_keys ||= []
|
48
|
+
if keys.present?
|
49
|
+
if keys.compact.present?
|
50
|
+
@mandrill_webhook_keys.concat(keys.flatten)
|
51
|
+
else
|
52
|
+
@mandrill_webhook_keys = []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@mandrill_webhook_keys
|
56
|
+
end
|
57
|
+
|
58
|
+
# Gets the current Mandrill WebHook Authentication key(s).
|
59
|
+
def mandrill_webhook_keys
|
60
|
+
authenticate_with_mandrill_keys!
|
61
|
+
end
|
62
|
+
|
63
|
+
# Command: directly assigns the WebHook key array to +keys+.
|
64
|
+
def mandrill_webhook_keys=(keys)
|
65
|
+
@mandrill_webhook_keys = Array(keys)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# Handles controller :show action (corresponds to a Mandrill "are you there?" test ping).
|
71
|
+
# Returns 200 and does nothing else.
|
41
72
|
def show
|
42
73
|
head(:ok)
|
43
74
|
end
|
44
75
|
|
76
|
+
# Handles controller :create action (corresponds to a POST from Mandrill).
|
45
77
|
def create
|
46
|
-
|
47
|
-
|
48
|
-
processor.run!
|
49
|
-
end
|
78
|
+
processor = Mandrill::WebHook::Processor.new(params, self)
|
79
|
+
processor.run!
|
50
80
|
head(:ok)
|
51
81
|
end
|
52
82
|
|
83
|
+
def authenticate_mandrill_request!
|
84
|
+
expected_signature = request.headers['HTTP_X_MANDRILL_SIGNATURE']
|
85
|
+
mandrill_webhook_keys = self.class.mandrill_webhook_keys
|
86
|
+
if Mandrill::WebHook::Processor.authentic?(expected_signature,mandrill_webhook_keys,request.original_url,request.params)
|
87
|
+
true
|
88
|
+
else
|
89
|
+
head(:forbidden, :text => "Mandrill signature did not match.")
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
53
93
|
|
54
|
-
end
|
94
|
+
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
class Mandrill::WebHook::Processor
|
2
2
|
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :params, :callback_host, :mandrill_events
|
4
4
|
|
5
5
|
# Command initialise the processor with +params+ Hash.
|
6
|
-
# +params+ is expected to contain an array of mandrill_events
|
7
|
-
|
8
|
-
|
6
|
+
# +params+ is expected to contain an array of mandrill_events.
|
7
|
+
# +callback_host+ is a handle to the controller making the request.
|
8
|
+
def initialize(params={},callback_host=nil)
|
9
|
+
@params = params
|
10
|
+
@callback_host = callback_host
|
11
|
+
end
|
12
|
+
|
13
|
+
def mandrill_events
|
14
|
+
@mandrill_events ||= JSON.parse(params['mandrill_events'] || '[]')
|
15
|
+
rescue
|
16
|
+
@mandrill_events = []
|
9
17
|
end
|
10
18
|
|
11
19
|
# Command: processes all +mandrill_events+
|
@@ -28,4 +36,29 @@ class Mandrill::WebHook::Processor
|
|
28
36
|
Mandrill::WebHook::EventDecorator[raw_event_payload]
|
29
37
|
end
|
30
38
|
|
39
|
+
class << self
|
40
|
+
|
41
|
+
# Returns true if +params+ sent to +original_url+ are authentic given +expected_signature+ and +mandrill_webhook_keys+.
|
42
|
+
def authentic?(expected_signature, mandrill_webhook_keys, original_url, params)
|
43
|
+
result = true
|
44
|
+
Array(mandrill_webhook_keys).each do |key|
|
45
|
+
signature = generate_signature(key, original_url, params)
|
46
|
+
result = (signature == expected_signature)
|
47
|
+
break if result
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Method described in docs: http://help.mandrill.com/entries/23704122-Authenticating-webhook-requests
|
53
|
+
def generate_signature(webhook_key, original_url, params)
|
54
|
+
signed_data = original_url.dup
|
55
|
+
params.except(:action, :controller).keys.sort.each do |key|
|
56
|
+
signed_data << key
|
57
|
+
signed_data << params[key]
|
58
|
+
end
|
59
|
+
Base64.encode64("#{OpenSSL::HMAC.digest('sha1', webhook_key, signed_data)}").strip
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
31
64
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"private_key": "ntynTMaFpM_octE5R1DKAw",
|
4
|
+
"original_url": "http://requestb.in/q9a918q9",
|
5
|
+
"headers" : {
|
6
|
+
"X-Mandrill-Signature": "7rUa4lNJitlb178MgGOcHO6T4Bg=",
|
7
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
8
|
+
"Host": "requestb.in",
|
9
|
+
"Connection": "close",
|
10
|
+
"Content-Length": 4091,
|
11
|
+
"User-Agent": "Mandrill-Webhook/1.0",
|
12
|
+
"Accept": "*/*"
|
13
|
+
},
|
14
|
+
"raw_params": {
|
15
|
+
"mandrill_events": "%5B%7B%22event%22%3A%22click%22%2C%22msg%22%3A%7B%22ts%22%3A1365109999%2C%22subject%22%3A%22This+an+example+webhook+message%22%2C%22email%22%3A%22example.webhook%40mandrillapp.com%22%2C%22sender%22%3A%22example.sender%40mandrillapp.com%22%2C%22tags%22%3A%5B%22webhook-example%22%5D%2C%22opens%22%3A%5B%7B%22ts%22%3A1365111111%7D%5D%2C%22clicks%22%3A%5B%7B%22ts%22%3A1365111111%2C%22url%22%3A%22http%3A%5C%2F%5C%2Fmandrill.com%22%7D%5D%2C%22state%22%3A%22sent%22%2C%22metadata%22%3A%7B%22user_id%22%3A111%7D%2C%22_id%22%3A%22exampleaaaaaaaaaaaaaaaaaaaaaaaaa%22%2C%22_version%22%3A%22exampleaaaaaaaaaaaaaaa%22%7D%2C%22ip%22%3A%22127.0.0.1%22%2C%22location%22%3A%7B%22country_short%22%3A%22US%22%2C%22country%22%3A%22United+States%22%2C%22region%22%3A%22Oklahoma%22%2C%22city%22%3A%22Oklahoma+City%22%2C%22latitude%22%3A35.4675598145%2C%22longitude%22%3A-97.5164337158%2C%22postal_code%22%3A%2273101%22%2C%22timezone%22%3A%22-05%3A00%22%7D%2C%22user_agent%22%3A%22Mozilla%5C%2F5.0+%28Macintosh%3B+U%3B+Intel+Mac+OS+X+10.6%3B+en-US%3B+rv%3A1.9.1.8%29+Gecko%5C%2F20100317+Postbox%5C%2F1.1.3%22%2C%22user_agent_parsed%22%3A%7B%22type%22%3A%22Email+Client%22%2C%22ua_family%22%3A%22Postbox%22%2C%22ua_name%22%3A%22Postbox+1.1.3%22%2C%22ua_version%22%3A%221.1.3%22%2C%22ua_url%22%3A%22http%3A%5C%2F%5C%2Fwww.postbox-inc.com%5C%2F%22%2C%22ua_company%22%3A%22Postbox%2C+Inc.%22%2C%22ua_company_url%22%3A%22http%3A%5C%2F%5C%2Fwww.postbox-inc.com%5C%2F%22%2C%22ua_icon%22%3A%22http%3A%5C%2F%5C%2Fcdn.mandrill.com%5C%2Fimg%5C%2Femail-client-icons%5C%2Fpostbox.png%22%2C%22os_family%22%3A%22OS+X%22%2C%22os_name%22%3A%22OS+X+10.6+Snow+Leopard%22%2C%22os_url%22%3A%22http%3A%5C%2F%5C%2Fwww.apple.com%5C%2Fosx%5C%2F%22%2C%22os_company%22%3A%22Apple+Computer%2C+Inc.%22%2C%22os_company_url%22%3A%22http%3A%5C%2F%5C%2Fwww.apple.com%5C%2F%22%2C%22os_icon%22%3A%22http%3A%5C%2F%5C%2Fcdn.mandrill.com%5C%2Fimg%5C%2Femail-client-icons%5C%2Fmacosx.png%22%2C%22mobile%22%3Afalse%7D%2C%22url%22%3A%22http%3A%5C%2F%5C%2Fmandrill.com%22%2C%22ts%22%3A1379233940%7D%2C%7B%22event%22%3A%22click%22%2C%22msg%22%3A%7B%22ts%22%3A1365109999%2C%22subject%22%3A%22This+an+example+webhook+message%22%2C%22email%22%3A%22example.webhook%40mandrillapp.com%22%2C%22sender%22%3A%22example.sender%40mandrillapp.com%22%2C%22tags%22%3A%5B%22webhook-example%22%5D%2C%22opens%22%3A%5B%7B%22ts%22%3A1365111111%7D%5D%2C%22clicks%22%3A%5B%7B%22ts%22%3A1365111111%2C%22url%22%3A%22http%3A%5C%2F%5C%2Fmandrill.com%22%7D%5D%2C%22state%22%3A%22sent%22%2C%22metadata%22%3A%7B%22user_id%22%3A111%7D%2C%22_id%22%3A%22exampleaaaaaaaaaaaaaaaaaaaaaaaaa%22%2C%22_version%22%3A%22exampleaaaaaaaaaaaaaaa%22%7D%2C%22ip%22%3A%22127.0.0.1%22%2C%22location%22%3A%7B%22country_short%22%3A%22US%22%2C%22country%22%3A%22United+States%22%2C%22region%22%3A%22Oklahoma%22%2C%22city%22%3A%22Oklahoma+City%22%2C%22latitude%22%3A35.4675598145%2C%22longitude%22%3A-97.5164337158%2C%22postal_code%22%3A%2273101%22%2C%22timezone%22%3A%22-05%3A00%22%7D%2C%22user_agent%22%3A%22Mozilla%5C%2F5.0+%28Macintosh%3B+U%3B+Intel+Mac+OS+X+10.6%3B+en-US%3B+rv%3A1.9.1.8%29+Gecko%5C%2F20100317+Postbox%5C%2F1.1.3%22%2C%22user_agent_parsed%22%3A%7B%22type%22%3A%22Email+Client%22%2C%22ua_family%22%3A%22Postbox%22%2C%22ua_name%22%3A%22Postbox+1.1.3%22%2C%22ua_version%22%3A%221.1.3%22%2C%22ua_url%22%3A%22http%3A%5C%2F%5C%2Fwww.postbox-inc.com%5C%2F%22%2C%22ua_company%22%3A%22Postbox%2C+Inc.%22%2C%22ua_company_url%22%3A%22http%3A%5C%2F%5C%2Fwww.postbox-inc.com%5C%2F%22%2C%22ua_icon%22%3A%22http%3A%5C%2F%5C%2Fcdn.mandrill.com%5C%2Fimg%5C%2Femail-client-icons%5C%2Fpostbox.png%22%2C%22os_family%22%3A%22OS+X%22%2C%22os_name%22%3A%22OS+X+10.6+Snow+Leopard%22%2C%22os_url%22%3A%22http%3A%5C%2F%5C%2Fwww.apple.com%5C%2Fosx%5C%2F%22%2C%22os_company%22%3A%22Apple+Computer%2C+Inc.%22%2C%22os_company_url%22%3A%22http%3A%5C%2F%5C%2Fwww.apple.com%5C%2F%22%2C%22os_icon%22%3A%22http%3A%5C%2F%5C%2Fcdn.mandrill.com%5C%2Fimg%5C%2Femail-client-icons%5C%2Fmacosx.png%22%2C%22mobile%22%3Afalse%7D%2C%22url%22%3A%22http%3A%5C%2F%5C%2Fmandrill.com%22%2C%22ts%22%3A1379233940%7D%5D"
|
16
|
+
},
|
17
|
+
"params": {
|
18
|
+
"mandrill_events": [{"event":"click","msg":{"ts":1365109999,"subject":"This an example webhook message","email":"example.webhook@mandrillapp.com","sender":"example.sender@mandrillapp.com","tags":["webhook-example"],"opens":[{"ts":1365111111}],"clicks":[{"ts":1365111111,"url":"http:\/\/mandrill.com"}],"state":"sent","metadata":{"user_id":111},"_id":"exampleaaaaaaaaaaaaaaaaaaaaaaaaa","_version":"exampleaaaaaaaaaaaaaaa"},"ip":"127.0.0.1","location":{"country_short":"US","country":"United States","region":"Oklahoma","city":"Oklahoma City","latitude":35.4675598145,"longitude":-97.5164337158,"postal_code":"73101","timezone":"-05:00"},"user_agent":"Mozilla\/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.8) Gecko\/20100317 Postbox\/1.1.3","user_agent_parsed":{"type":"Email Client","ua_family":"Postbox","ua_name":"Postbox 1.1.3","ua_version":"1.1.3","ua_url":"http:\/\/www.postbox-inc.com\/","ua_company":"Postbox, Inc.","ua_company_url":"http:\/\/www.postbox-inc.com\/","ua_icon":"http:\/\/cdn.mandrill.com\/img\/email-client-icons\/postbox.png","os_family":"OS X","os_name":"OS X 10.6 Snow Leopard","os_url":"http:\/\/www.apple.com\/osx\/","os_company":"Apple Computer, Inc.","os_company_url":"http:\/\/www.apple.com\/","os_icon":"http:\/\/cdn.mandrill.com\/img\/email-client-icons\/macosx.png","mobile":false},"url":"http:\/\/mandrill.com","ts":1379233940},{"event":"click","msg":{"ts":1365109999,"subject":"This an example webhook message","email":"example.webhook@mandrillapp.com","sender":"example.sender@mandrillapp.com","tags":["webhook-example"],"opens":[{"ts":1365111111}],"clicks":[{"ts":1365111111,"url":"http:\/\/mandrill.com"}],"state":"sent","metadata":{"user_id":111},"_id":"exampleaaaaaaaaaaaaaaaaaaaaaaaaa","_version":"exampleaaaaaaaaaaaaaaa"},"ip":"127.0.0.1","location":{"country_short":"US","country":"United States","region":"Oklahoma","city":"Oklahoma City","latitude":35.4675598145,"longitude":-97.5164337158,"postal_code":"73101","timezone":"-05:00"},"user_agent":"Mozilla\/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.8) Gecko\/20100317 Postbox\/1.1.3","user_agent_parsed":{"type":"Email Client","ua_family":"Postbox","ua_name":"Postbox 1.1.3","ua_version":"1.1.3","ua_url":"http:\/\/www.postbox-inc.com\/","ua_company":"Postbox, Inc.","ua_company_url":"http:\/\/www.postbox-inc.com\/","ua_icon":"http:\/\/cdn.mandrill.com\/img\/email-client-icons\/postbox.png","os_family":"OS X","os_name":"OS X 10.6 Snow Leopard","os_url":"http:\/\/www.apple.com\/osx\/","os_company":"Apple Computer, Inc.","os_company_url":"http:\/\/www.apple.com\/","os_icon":"http:\/\/cdn.mandrill.com\/img\/email-client-icons\/macosx.png","mobile":false},"url":"http:\/\/mandrill.com","ts":1379233940}]
|
19
|
+
}
|
20
|
+
}
|
21
|
+
]
|
@@ -1,17 +1,76 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
class WebHookProcessorTestHarness
|
4
|
-
|
4
|
+
# Mock some controller behaviour
|
5
|
+
# TODO: we should probably really start using a real controller harness for testing
|
6
|
+
def self.skip_before_filter(*args) ; @skip_before_filter_settings = args; end
|
7
|
+
def self.skip_before_filter_settings ; @skip_before_filter_settings; end
|
8
|
+
def self.before_filter(*args) ; @before_filter_settings = args ; end
|
9
|
+
def self.before_filter_settings ; @before_filter_settings; end
|
5
10
|
def head(*args) ; end
|
6
|
-
attr_accessor :params
|
11
|
+
attr_accessor :params, :request
|
7
12
|
|
8
13
|
include Mandrill::Rails::WebHookProcessor
|
9
14
|
end
|
10
15
|
|
11
16
|
describe Mandrill::Rails::WebHookProcessor do
|
12
|
-
let(:
|
13
|
-
let(:
|
14
|
-
before
|
17
|
+
let(:processor_class) { WebHookProcessorTestHarness }
|
18
|
+
let(:processor_instance) { processor_class.new }
|
19
|
+
before do
|
20
|
+
processor_class.authenticate_with_mandrill_keys! nil
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "##skip_before_filter settings" do
|
24
|
+
subject { processor_class.skip_before_filter_settings }
|
25
|
+
it { should eql([:verify_authenticity_token]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "##before_filter settings" do
|
29
|
+
subject { processor_class.before_filter_settings }
|
30
|
+
it { should eql([:authenticate_mandrill_request!, {:only=>[:create]}]) }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "##authenticate_with_mandrill_keys!" do
|
34
|
+
subject { processor_class.mandrill_webhook_keys }
|
35
|
+
it { should eql([]) }
|
36
|
+
context "when set with a single value" do
|
37
|
+
let(:key_a) { "key_a" }
|
38
|
+
before { processor_class.authenticate_with_mandrill_keys! key_a }
|
39
|
+
it { should eql([key_a]) }
|
40
|
+
context "then called with nil" do
|
41
|
+
before { processor_class.authenticate_with_mandrill_keys! nil }
|
42
|
+
it { should eql([]) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context "when set with a list of values" do
|
46
|
+
let(:key_a) { "key_a" }
|
47
|
+
let(:key_b) { "key_b" }
|
48
|
+
before { processor_class.authenticate_with_mandrill_keys! key_a, key_b }
|
49
|
+
it { should eql([key_a,key_b]) }
|
50
|
+
end
|
51
|
+
context "when set with an explicit array of values" do
|
52
|
+
let(:key_a) { "key_a" }
|
53
|
+
let(:key_b) { "key_b" }
|
54
|
+
before { processor_class.authenticate_with_mandrill_keys! [key_a, key_b] }
|
55
|
+
it { should eql([key_a,key_b]) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#mandrill_webhook_keys" do
|
60
|
+
subject { processor_class.mandrill_webhook_keys }
|
61
|
+
it { should eql([]) }
|
62
|
+
context "when set with mandrill_webhook_keys=" do
|
63
|
+
let(:expected_value) { [1,2,3] }
|
64
|
+
before { processor_class.mandrill_webhook_keys = expected_value }
|
65
|
+
it { should eql(expected_value) }
|
66
|
+
end
|
67
|
+
context "when set with authenticate_with_mandrill_keys!" do
|
68
|
+
let(:expected_value) { [4,5,6] }
|
69
|
+
before { processor_class.authenticate_with_mandrill_keys! expected_value }
|
70
|
+
it { should eql(expected_value) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
15
74
|
|
16
75
|
subject { processor_instance }
|
17
76
|
|
@@ -23,11 +82,58 @@ describe Mandrill::Rails::WebHookProcessor do
|
|
23
82
|
end
|
24
83
|
|
25
84
|
describe "#create" do
|
85
|
+
let(:params) { {} }
|
86
|
+
before do
|
87
|
+
processor_instance.params = params
|
88
|
+
end
|
26
89
|
it "should return head(:ok)" do
|
27
|
-
Mandrill::WebHook::Processor.any_instance.should_receive(:run!)
|
28
90
|
processor_instance.should_receive(:head).with(:ok)
|
91
|
+
Mandrill::WebHook::Processor.any_instance.should_receive(:run!)
|
29
92
|
processor_instance.create
|
30
93
|
end
|
31
94
|
end
|
32
95
|
|
96
|
+
describe "#authenticate_mandrill_request! (protected)" do
|
97
|
+
let(:example_payload) { webhook_example_event('click_with_signature') }
|
98
|
+
let(:expected_signature) { example_payload['headers']['X-Mandrill-Signature'] }
|
99
|
+
let(:original_url) { example_payload['original_url'] }
|
100
|
+
let(:valid_webhook_key) { example_payload['private_key'] }
|
101
|
+
let(:raw_params) { example_payload['raw_params'] }
|
102
|
+
let(:params) { {} }
|
103
|
+
let(:headers) { { 'HTTP_X_MANDRILL_SIGNATURE' => expected_signature} }
|
104
|
+
let(:request) { double() }
|
105
|
+
before do
|
106
|
+
request.stub(:original_url).and_return(original_url)
|
107
|
+
request.stub(:params).and_return(raw_params)
|
108
|
+
request.stub(:headers).and_return(headers)
|
109
|
+
processor_instance.request = request
|
110
|
+
processor_instance.params = params
|
111
|
+
end
|
112
|
+
subject { processor_instance.send(:authenticate_mandrill_request!) }
|
113
|
+
|
114
|
+
context "when authentication not enabled" do
|
115
|
+
it { should be_true }
|
116
|
+
end
|
117
|
+
context "when authentication enabled" do
|
118
|
+
before do
|
119
|
+
processor_class.authenticate_with_mandrill_keys! mandrill_webhook_keys
|
120
|
+
end
|
121
|
+
context "with valid key" do
|
122
|
+
let(:mandrill_webhook_keys) { valid_webhook_key }
|
123
|
+
it { should be_true }
|
124
|
+
end
|
125
|
+
context "with mix of valid and invalid keys" do
|
126
|
+
let(:mandrill_webhook_keys) { ['bogative',valid_webhook_key] }
|
127
|
+
it { should be_true }
|
128
|
+
end
|
129
|
+
context "with invalid key" do
|
130
|
+
let(:mandrill_webhook_keys) { 'bogative' }
|
131
|
+
it "should call head(:forbidden) and return false" do
|
132
|
+
processor_instance.should_receive(:head).with(:forbidden, :text => "Mandrill signature did not match.")
|
133
|
+
subject.should be_false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
33
139
|
end
|
@@ -3,12 +3,13 @@ require 'spec_helper'
|
|
3
3
|
describe Mandrill::WebHook::Processor do
|
4
4
|
|
5
5
|
let(:params) { {} }
|
6
|
-
let(:
|
6
|
+
let(:processor_class) { Mandrill::WebHook::Processor }
|
7
|
+
let(:processor) { processor_class.new(params) }
|
7
8
|
|
8
9
|
describe "#run!" do
|
9
10
|
context "with inbound events" do
|
10
11
|
before do
|
11
|
-
|
12
|
+
processor_class.stub(:handle_inbound)
|
12
13
|
end
|
13
14
|
let(:event1) { { "event" => "inbound" } }
|
14
15
|
let(:event2) { { "event" => "inbound" } }
|
@@ -18,6 +19,21 @@ describe Mandrill::WebHook::Processor do
|
|
18
19
|
processor.run!
|
19
20
|
end
|
20
21
|
end
|
22
|
+
context "with callback host" do
|
23
|
+
let(:callback_host) do
|
24
|
+
host = double()
|
25
|
+
host.stub(:handle_inbound)
|
26
|
+
host
|
27
|
+
end
|
28
|
+
let(:processor) { processor_class.new(params,callback_host) }
|
29
|
+
let(:event1) { { "event" => "inbound" } }
|
30
|
+
let(:event2) { { "event" => "inbound" } }
|
31
|
+
let(:params) { { "mandrill_events" => [event1,event2].to_json } }
|
32
|
+
it "should pass event payload to the handler" do
|
33
|
+
callback_host.should_receive(:handle_inbound).twice
|
34
|
+
processor.run!
|
35
|
+
end
|
36
|
+
end
|
21
37
|
end
|
22
38
|
|
23
39
|
describe "#wrap_payload" do
|
@@ -26,4 +42,41 @@ describe Mandrill::WebHook::Processor do
|
|
26
42
|
its(:class) { should eql(Mandrill::WebHook::EventDecorator) }
|
27
43
|
end
|
28
44
|
|
45
|
+
describe "##authentic?" do
|
46
|
+
let(:example_payload) { webhook_example_event('click_with_signature') }
|
47
|
+
let(:expected_signature) { example_payload['headers']['X-Mandrill-Signature'] }
|
48
|
+
let(:original_url) { example_payload['original_url'] }
|
49
|
+
let(:webhook_key) { example_payload['private_key'] }
|
50
|
+
let(:mandrill_webhook_keys) { [webhook_key] }
|
51
|
+
let(:params) { example_payload['raw_params'] }
|
52
|
+
subject { processor_class.authentic?(expected_signature, mandrill_webhook_keys, original_url, params) }
|
53
|
+
context "when valid" do
|
54
|
+
it { should be_true }
|
55
|
+
end
|
56
|
+
context "when no keys" do
|
57
|
+
let(:mandrill_webhook_keys) { [] }
|
58
|
+
it { should be_true }
|
59
|
+
end
|
60
|
+
context "when keys don't match" do
|
61
|
+
let(:mandrill_webhook_keys) { ['bogative'] }
|
62
|
+
it { should be_false }
|
63
|
+
end
|
64
|
+
context "when signature don't match" do
|
65
|
+
let(:expected_signature) { 'bogative' }
|
66
|
+
it { should be_false }
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "##generate_signature" do
|
73
|
+
let(:example_payload) { webhook_example_event('click_with_signature') }
|
74
|
+
let(:expected_signature) { example_payload['headers']['X-Mandrill-Signature'] }
|
75
|
+
let(:original_url) { example_payload['original_url'] }
|
76
|
+
let(:webhook_key) { example_payload['private_key'] }
|
77
|
+
let(:params) { example_payload['raw_params'] }
|
78
|
+
subject { processor_class.generate_signature(webhook_key, original_url, params) }
|
79
|
+
it { should eql(expected_signature) }
|
80
|
+
end
|
81
|
+
|
29
82
|
end
|
@@ -15,7 +15,11 @@ module FixturesHelper
|
|
15
15
|
|
16
16
|
# Returns the JSON representation of an +sample_name+ event
|
17
17
|
def webhook_example_event(sample_name)
|
18
|
-
webhook_example_events(sample_name).first
|
18
|
+
data = webhook_example_events(sample_name).first
|
19
|
+
if data['raw_params'] && (mandrill_events_data = data['raw_params']['mandrill_events'])
|
20
|
+
data['raw_params']['mandrill_events'] = URI.decode_www_form_component(mandrill_events_data)
|
21
|
+
end
|
22
|
+
data
|
19
23
|
end
|
20
24
|
|
21
25
|
def payload_examples_path
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mandrill-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -115,7 +115,6 @@ extensions: []
|
|
115
115
|
extra_rdoc_files: []
|
116
116
|
files:
|
117
117
|
- .gitignore
|
118
|
-
- .rspec
|
119
118
|
- .travis.yml
|
120
119
|
- CHANGELOG
|
121
120
|
- Gemfile
|
@@ -135,6 +134,7 @@ files:
|
|
135
134
|
- spec/fixtures/payload_examples/sample.pdf
|
136
135
|
- spec/fixtures/payload_examples/sample.txt
|
137
136
|
- spec/fixtures/webhook_examples/click.json
|
137
|
+
- spec/fixtures/webhook_examples/click_with_signature.json
|
138
138
|
- spec/fixtures/webhook_examples/inbound.json
|
139
139
|
- spec/fixtures/webhook_examples/inbound_reply.json
|
140
140
|
- spec/fixtures/webhook_examples/inbound_with_multiple_attachments.json
|
@@ -168,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
168
|
version: '0'
|
169
169
|
requirements: []
|
170
170
|
rubyforge_project:
|
171
|
-
rubygems_version: 1.8.
|
171
|
+
rubygems_version: 1.8.25
|
172
172
|
signing_key:
|
173
173
|
specification_version: 3
|
174
174
|
summary: Provides webhook processing and event decoration to make using Mandrill with
|
@@ -178,6 +178,7 @@ test_files:
|
|
178
178
|
- spec/fixtures/payload_examples/sample.pdf
|
179
179
|
- spec/fixtures/payload_examples/sample.txt
|
180
180
|
- spec/fixtures/webhook_examples/click.json
|
181
|
+
- spec/fixtures/webhook_examples/click_with_signature.json
|
181
182
|
- spec/fixtures/webhook_examples/inbound.json
|
182
183
|
- spec/fixtures/webhook_examples/inbound_reply.json
|
183
184
|
- spec/fixtures/webhook_examples/inbound_with_multiple_attachments.json
|
data/.rspec
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|