Skip to content

Commit 7e609b9

Browse files
committed
Add support for reCAPTCHA to WebhookAgent
1 parent 10a2c10 commit 7e609b9

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

app/models/agents/webhook_agent.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module Agents
22
class WebhookAgent < Agent
3+
include WebRequestConcern
4+
35
cannot_be_scheduled!
46
cannot_receive_events!
57

@@ -24,6 +26,8 @@ class WebhookAgent < Agent
2426
For example, "post,get" will enable POST and GET requests. Defaults
2527
to "post".
2628
* `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).
2731
MD
2832
end
2933

@@ -46,10 +50,36 @@ def receive_web_request(params, method, format)
4650
secret = params.delete('secret')
4751
return ["Not Authorized", 401] unless secret == interpolated['secret']
4852

49-
#check the verbs
53+
# check the verbs
5054
verbs = (interpolated['verbs'] || 'post').split(/,/).map { |x| x.strip.downcase }.select { |x| x.present? }
5155
return ["Please use #{verbs.join('/').upcase} requests only", 401] unless verbs.include?(method)
5256

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+
5383
[payload_for(params)].flatten.each do |payload|
5484
create_event(payload: payload)
5585
end

spec/models/agents/webhook_agent_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,80 @@
223223

224224
end
225225

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+
226300
end
227301

228302
end

0 commit comments

Comments
 (0)