/
controller_ext.rb
110 lines (94 loc) 路 3.22 KB
/
controller_ext.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# frozen_string_literal: true
module InvisibleCaptcha
module ControllerExt
module ClassMethods
def invisible_captcha(options = {})
if respond_to?(:before_action)
before_action(options) do
detect_spam(options)
end
else
before_filter(options) do
detect_spam(options)
end
end
end
end
private
def detect_spam(options = {})
if timestamp_spam?(options)
on_timestamp_spam(options)
elsif honeypot_spam?(options)
on_spam(options)
end
end
def on_timestamp_spam(options = {})
if action = options[:on_timestamp_spam]
send(action)
else
if respond_to?(:redirect_back)
redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
else
redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
end
end
end
def on_spam(options = {})
if action = options[:on_spam]
send(action)
else
head(200)
end
end
def timestamp_spam?(options = {})
enabled = if options.key?(:timestamp_enabled)
options[:timestamp_enabled]
else
InvisibleCaptcha.timestamp_enabled
end
return false unless enabled
@invisible_captcha_timestamp ||= session.delete(:invisible_captcha_timestamp)
# Consider as spam if timestamp not in session, cause that means the form was not fetched at all
unless @invisible_captcha_timestamp
warn("Invisible Captcha timestamp not found in session.")
return true
end
time_to_submit = Time.zone.now - DateTime.iso8601(@invisible_captcha_timestamp)
threshold = options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold
# Consider as spam if form submitted too quickly
if time_to_submit < threshold
warn("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
return true
end
return false
end
def honeypot_spam?(options = {})
honeypot = options[:honeypot]
scope = options[:scope] || controller_name.singularize
if honeypot
# If honeypot is defined for this controller-action, search for:
# - honeypot: params[:subtitle]
# - honeypot with scope: params[:topic][:subtitle]
if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
warn("Invisible Captcha honeypot param '#{honeypot}' was present.")
return true
else
# No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
params.delete(honeypot) if params.key?(honeypot)
params[scope].try(:delete, honeypot) if params.key?(scope)
end
else
InvisibleCaptcha.honeypots.each do |default_honeypot|
if params[default_honeypot].present?
warn("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
return true
end
end
end
false
end
def warn(message)
logger.warn("Potential spam detected for IP #{request.remote_ip}. #{message}")
end
end
end