Merge pull request #1145 from cantino/webhook_recaptcha

Add support for reCAPTCHA to WebhookAgent

Akinori MUSHA 8 years ago
parent
commit
915fa97c4f
2 changed files with 105 additions and 1 deletions
  1. 31 1
      app/models/agents/webhook_agent.rb
  2. 74 0
      spec/models/agents/webhook_agent_spec.rb

+ 31 - 1
app/models/agents/webhook_agent.rb

@@ -1,5 +1,7 @@
1 1
 module Agents
2 2
   class WebhookAgent < Agent
3
+    include WebRequestConcern
4
+
3 5
     cannot_be_scheduled!
4 6
     cannot_receive_events!
5 7
 
@@ -24,6 +26,8 @@ module Agents
24 26
           For example, "post,get" will enable POST and GET requests. Defaults
25 27
           to "post".
26 28
         * `response` - The response message to the request. Defaults to 'Event Created'.
29
+        * `recaptcha_secret` - Setting this to a reCAPTCHA "secret" key makes your agent verify incoming requests with reCAPTCHA.  Don't forget to embed a reCAPTCHA snippet including your "site" key in the originating form(s).
30
+        * `recaptcha_send_remote_addr` - Set this to true if your server is properly configured to set REMOTE_ADDR to the IP address of each visitor (instead of that of a proxy server).
27 31
       MD
28 32
     end
29 33
 
@@ -46,10 +50,36 @@ module Agents
46 50
       secret = params.delete('secret')
47 51
       return ["Not Authorized", 401] unless secret == interpolated['secret']
48 52
 
49
-      #check the verbs
53
+      # check the verbs
50 54
       verbs = (interpolated['verbs'] || 'post').split(/,/).map { |x| x.strip.downcase }.select { |x| x.present? }
51 55
       return ["Please use #{verbs.join('/').upcase} requests only", 401] unless verbs.include?(method)
52 56
 
57
+      # check the reCAPTCHA response if required
58
+      if recaptcha_secret = interpolated['recaptcha_secret'].presence
59
+        recaptcha_response = params.delete('g-recaptcha-response') or
60
+          return ["Not Authorized", 401]
61
+
62
+        parameters = {
63
+          secret: recaptcha_secret,
64
+          response: recaptcha_response,
65
+        }
66
+
67
+        if boolify(interpolated['recaptcha_send_remote_addr'])
68
+          parameters[:remoteip] = request.env['REMOTE_ADDR']
69
+        end
70
+
71
+        begin
72
+          response = faraday.post('https://www.google.com/recaptcha/api/siteverify',
73
+                                  parameters)
74
+        rescue => e
75
+          error "Verification failed: #{e.message}"
76
+          return ["Not Authorized", 401]
77
+        end
78
+
79
+        JSON.parse(response.body)['success'] or
80
+          return ["Not Authorized", 401]
81
+      end
82
+
53 83
       [payload_for(params)].flatten.each do |payload|
54 84
         create_event(payload: payload)
55 85
       end

+ 74 - 0
spec/models/agents/webhook_agent_spec.rb

@@ -223,6 +223,80 @@ describe Agents::WebhookAgent do
223 223
 
224 224
       end
225 225
 
226
+      context "with reCAPTCHA" do
227
+        it "should not check a reCAPTCHA response unless recaptcha_secret is set" do
228
+          checked = false
229
+          out = nil
230
+
231
+          stub_request(:any, /verify/).to_return { |request|
232
+            checked = true
233
+            { status: 200, body: '{"success":false}' }
234
+          }
235
+
236
+          expect {
237
+            out= agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
238
+          }.not_to change { checked }
239
+
240
+          expect(out).to eq(["Event Created", 201])
241
+        end
242
+
243
+        it "should reject a request if recaptcha_secret is set but g-recaptcha-response is not given" do
244
+          agent.options['recaptcha_secret'] = 'supersupersecret'
245
+
246
+          checked = false
247
+          out = nil
248
+
249
+          stub_request(:any, /verify/).to_return { |request|
250
+            checked = true
251
+            { status: 200, body: '{"success":false}' }
252
+          }
253
+
254
+          expect {
255
+            out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
256
+          }.not_to change { checked }
257
+
258
+          expect(out).to eq(["Not Authorized", 401])
259
+        end
260
+
261
+        it "should reject a request if recaptcha_secret is set and g-recaptcha-response given is not verified" do
262
+          agent.options['recaptcha_secret'] = 'supersupersecret'
263
+
264
+          checked = false
265
+          out = nil
266
+
267
+          stub_request(:any, /verify/).to_return { |request|
268
+            checked = true
269
+            { status: 200, body: '{"success":false}' }
270
+          }
271
+
272
+          expect {
273
+            out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload, 'g-recaptcha-response' => 'somevalue' }, "post", "text/html")
274
+          }.to change { checked }
275
+
276
+          expect(out).to eq(["Not Authorized", 401])
277
+        end
278
+
279
+        it "should accept a request if recaptcha_secret is set and g-recaptcha-response given is verified" do
280
+          agent.options['payload_path'] = '.'
281
+          agent.options['recaptcha_secret'] = 'supersupersecret'
282
+
283
+          checked = false
284
+          out = nil
285
+
286
+          stub_request(:any, /verify/).to_return { |request|
287
+            checked = true
288
+            { status: 200, body: '{"success":true}' }
289
+          }
290
+
291
+          expect {
292
+            out = agent.receive_web_request(payload.merge({ 'secret' => 'foobar', 'g-recaptcha-response' => 'somevalue' }), "post", "text/html")
293
+          }.to change { checked }
294
+
295
+          expect(out).to eq(["Event Created", 201])
296
+          expect(Event.last.payload).to eq(payload)
297
+        end
298
+      end
299
+
226 300
     end
227 301
 
228 302
   end