recaptcha 4.11.0 → 5.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +428 -74
- data/lib/recaptcha/adapters/controller_methods.rb +96 -0
- data/lib/recaptcha/adapters/view_methods.rb +26 -0
- data/lib/recaptcha/configuration.rb +29 -5
- data/lib/recaptcha/helpers.rb +332 -0
- data/lib/recaptcha/rails.rb +1 -0
- data/lib/recaptcha/railtie.rb +24 -4
- data/lib/recaptcha/version.rb +1 -1
- data/lib/recaptcha.rb +119 -30
- data/rails/locales/en.yml +5 -0
- data/rails/locales/fr.yml +5 -0
- data/rails/locales/ja.yml +5 -0
- data/rails/locales/nl.yml +5 -0
- metadata +16 -25
- data/lib/recaptcha/client_helper.rb +0 -140
- data/lib/recaptcha/verify.rb +0 -107
data/lib/recaptcha.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'uri'
|
3
|
+
require 'json'
|
5
4
|
require 'net/http'
|
5
|
+
require 'uri'
|
6
6
|
|
7
|
+
require 'recaptcha/configuration'
|
8
|
+
require 'recaptcha/helpers'
|
9
|
+
require 'recaptcha/adapters/controller_methods'
|
10
|
+
require 'recaptcha/adapters/view_methods'
|
7
11
|
if defined?(Rails)
|
8
12
|
require 'recaptcha/railtie'
|
9
|
-
else
|
10
|
-
require 'recaptcha/client_helper'
|
11
|
-
require 'recaptcha/verify'
|
12
13
|
end
|
13
14
|
|
14
15
|
module Recaptcha
|
15
|
-
CONFIG = {
|
16
|
-
'server_url' => 'https://www.google.com/recaptcha/api.js',
|
17
|
-
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
|
18
|
-
}.freeze
|
19
|
-
|
20
|
-
USE_SSL_BY_DEFAULT = false
|
21
|
-
HANDLE_TIMEOUTS_GRACEFULLY = true
|
22
16
|
DEFAULT_TIMEOUT = 3
|
23
17
|
|
18
|
+
class RecaptchaError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
class VerifyError < RecaptchaError
|
22
|
+
end
|
23
|
+
|
24
24
|
# Gives access to the current Configuration.
|
25
25
|
def self.configuration
|
26
26
|
@configuration ||= Configuration.new
|
@@ -50,33 +50,122 @@ module Recaptcha
|
|
50
50
|
original_config.each { |key, value| configuration.send("#{key}=", value) }
|
51
51
|
end
|
52
52
|
|
53
|
-
def self.
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
def self.skip_env?(env)
|
54
|
+
configuration.skip_verify_env.include?(env || configuration.default_env)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.invalid_response?(resp)
|
58
|
+
resp.empty? || resp.length > configuration.response_limit
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.verify_via_api_call(response, options)
|
62
|
+
if Recaptcha.configuration.enterprise
|
63
|
+
verify_via_api_call_enterprise(response, options)
|
57
64
|
else
|
58
|
-
|
65
|
+
verify_via_api_call_free(response, options)
|
59
66
|
end
|
60
|
-
query = URI.encode_www_form(verify_hash)
|
61
|
-
uri = URI.parse(Recaptcha.configuration.verify_url + '?' + query)
|
62
|
-
http_instance = http.new(uri.host, uri.port)
|
63
|
-
http_instance.read_timeout = http_instance.open_timeout = options[:timeout] || DEFAULT_TIMEOUT
|
64
|
-
http_instance.use_ssl = true if uri.port == 443
|
65
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
66
|
-
http_instance.request(request).body
|
67
67
|
end
|
68
68
|
|
69
|
-
def self.
|
70
|
-
|
71
|
-
|
69
|
+
def self.verify_via_api_call_enterprise(response, options)
|
70
|
+
site_key = options.fetch(:site_key) { configuration.site_key! }
|
71
|
+
api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! }
|
72
|
+
project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! }
|
73
|
+
|
74
|
+
query_params = { 'key' => api_key }
|
75
|
+
body = { 'event' => { 'token' => response, 'siteKey' => site_key } }
|
76
|
+
body['event']['expectedAction'] = options[:action] if options.key?(:action)
|
77
|
+
body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
|
78
|
+
|
79
|
+
reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
|
80
|
+
token_properties = reply['tokenProperties']
|
81
|
+
success = !token_properties.nil? &&
|
82
|
+
token_properties['valid'].to_s == 'true' &&
|
83
|
+
hostname_valid?(token_properties['hostname'], options[:hostname]) &&
|
84
|
+
action_valid?(token_properties['action'], options[:action]) &&
|
85
|
+
score_above_threshold?(reply['score'], options[:minimum_score]) &&
|
86
|
+
score_below_threshold?(reply['score'], options[:maximum_score])
|
87
|
+
|
88
|
+
if options[:with_reply] == true
|
89
|
+
[success, reply]
|
72
90
|
else
|
73
|
-
|
91
|
+
success
|
74
92
|
end
|
75
93
|
end
|
76
94
|
|
77
|
-
|
95
|
+
def self.verify_via_api_call_free(response, options)
|
96
|
+
secret_key = options.fetch(:secret_key) { configuration.secret_key! }
|
97
|
+
verify_hash = { 'secret' => secret_key, 'response' => response }
|
98
|
+
verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
|
99
|
+
|
100
|
+
reply = api_verification_free(verify_hash, timeout: options[:timeout])
|
101
|
+
success = reply['success'].to_s == 'true' &&
|
102
|
+
hostname_valid?(reply['hostname'], options[:hostname]) &&
|
103
|
+
action_valid?(reply['action'], options[:action]) &&
|
104
|
+
score_above_threshold?(reply['score'], options[:minimum_score]) &&
|
105
|
+
score_below_threshold?(reply['score'], options[:maximum_score])
|
106
|
+
|
107
|
+
if options[:with_reply] == true
|
108
|
+
[success, reply]
|
109
|
+
else
|
110
|
+
success
|
111
|
+
end
|
78
112
|
end
|
79
113
|
|
80
|
-
|
114
|
+
def self.hostname_valid?(hostname, validation)
|
115
|
+
validation ||= configuration.hostname
|
116
|
+
|
117
|
+
case validation
|
118
|
+
when nil, FalseClass then true
|
119
|
+
when String then validation == hostname
|
120
|
+
else validation.call(hostname)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.action_valid?(action, expected_action)
|
125
|
+
case expected_action
|
126
|
+
when nil, FalseClass then true
|
127
|
+
else action == expected_action
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.score_above_threshold?(score, minimum_score)
|
132
|
+
!minimum_score || (score && score >= minimum_score)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.score_below_threshold?(score, maximum_score)
|
136
|
+
!maximum_score || (score && score <= maximum_score)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.http_client_for(uri:, timeout: nil)
|
140
|
+
timeout ||= DEFAULT_TIMEOUT
|
141
|
+
http = if configuration.proxy
|
142
|
+
proxy_server = URI.parse(configuration.proxy)
|
143
|
+
Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
|
144
|
+
else
|
145
|
+
Net::HTTP
|
146
|
+
end
|
147
|
+
instance = http.new(uri.host, uri.port)
|
148
|
+
instance.read_timeout = instance.open_timeout = timeout
|
149
|
+
instance.use_ssl = true if uri.port == 443
|
150
|
+
|
151
|
+
instance
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.api_verification_free(verify_hash, timeout: nil)
|
155
|
+
query = URI.encode_www_form(verify_hash)
|
156
|
+
uri = URI.parse("#{configuration.verify_url}?#{query}")
|
157
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
158
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
159
|
+
JSON.parse(http_instance.request(request).body)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
|
163
|
+
query = URI.encode_www_form(query_params)
|
164
|
+
uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
|
165
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
166
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
167
|
+
request['Content-Type'] = 'application/json; charset=utf-8'
|
168
|
+
request.body = JSON.generate(body)
|
169
|
+
JSON.parse(http_instance.request(request).body)
|
81
170
|
end
|
82
171
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason L Perry
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: activesupport
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: i18n
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,17 +147,23 @@ files:
|
|
161
147
|
- LICENSE
|
162
148
|
- README.md
|
163
149
|
- lib/recaptcha.rb
|
164
|
-
- lib/recaptcha/
|
150
|
+
- lib/recaptcha/adapters/controller_methods.rb
|
151
|
+
- lib/recaptcha/adapters/view_methods.rb
|
165
152
|
- lib/recaptcha/configuration.rb
|
153
|
+
- lib/recaptcha/helpers.rb
|
166
154
|
- lib/recaptcha/rails.rb
|
167
155
|
- lib/recaptcha/railtie.rb
|
168
|
-
- lib/recaptcha/verify.rb
|
169
156
|
- lib/recaptcha/version.rb
|
157
|
+
- rails/locales/en.yml
|
158
|
+
- rails/locales/fr.yml
|
159
|
+
- rails/locales/ja.yml
|
160
|
+
- rails/locales/nl.yml
|
170
161
|
homepage: http://github.com/ambethia/recaptcha
|
171
162
|
licenses:
|
172
163
|
- MIT
|
173
|
-
metadata:
|
174
|
-
|
164
|
+
metadata:
|
165
|
+
source_code_uri: https://github.com/ambethia/recaptcha
|
166
|
+
post_install_message:
|
175
167
|
rdoc_options: []
|
176
168
|
require_paths:
|
177
169
|
- lib
|
@@ -179,16 +171,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
179
171
|
requirements:
|
180
172
|
- - ">="
|
181
173
|
- !ruby/object:Gem::Version
|
182
|
-
version: 2.
|
174
|
+
version: 2.7.0
|
183
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
176
|
requirements:
|
185
177
|
- - ">="
|
186
178
|
- !ruby/object:Gem::Version
|
187
179
|
version: '0'
|
188
180
|
requirements: []
|
189
|
-
|
190
|
-
|
191
|
-
signing_key:
|
181
|
+
rubygems_version: 3.3.3
|
182
|
+
signing_key:
|
192
183
|
specification_version: 4
|
193
184
|
summary: Helpers for the reCAPTCHA API
|
194
185
|
test_files: []
|
@@ -1,140 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Recaptcha
|
4
|
-
module ClientHelper
|
5
|
-
# Your public API can be specified in the +options+ hash or preferably
|
6
|
-
# using the Configuration.
|
7
|
-
def recaptcha_tags(options = {})
|
8
|
-
if options.key?(:stoken)
|
9
|
-
raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
|
10
|
-
end
|
11
|
-
if options.key?(:ssl)
|
12
|
-
raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
|
13
|
-
end
|
14
|
-
|
15
|
-
noscript = options.delete(:noscript)
|
16
|
-
|
17
|
-
html, tag_attributes, fallback_uri = Recaptcha::ClientHelper.recaptcha_components(options)
|
18
|
-
html << %(<div #{tag_attributes}></div>\n)
|
19
|
-
|
20
|
-
if noscript != false
|
21
|
-
html << <<-HTML
|
22
|
-
<noscript>
|
23
|
-
<div>
|
24
|
-
<div style="width: 302px; height: 422px; position: relative;">
|
25
|
-
<div style="width: 302px; height: 422px; position: absolute;">
|
26
|
-
<iframe
|
27
|
-
src="#{fallback_uri}"
|
28
|
-
scrolling="no" name="ReCAPTCHA"
|
29
|
-
style="width: 302px; height: 422px; border-style: none; border: 0;">
|
30
|
-
</iframe>
|
31
|
-
</div>
|
32
|
-
</div>
|
33
|
-
<div style="width: 300px; height: 60px; border-style: none;
|
34
|
-
bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
|
35
|
-
background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
|
36
|
-
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
|
37
|
-
class="g-recaptcha-response"
|
38
|
-
style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
|
39
|
-
margin: 10px 25px; padding: 0px; resize: none;">
|
40
|
-
</textarea>
|
41
|
-
</div>
|
42
|
-
</div>
|
43
|
-
</noscript>
|
44
|
-
HTML
|
45
|
-
end
|
46
|
-
|
47
|
-
html.respond_to?(:html_safe) ? html.html_safe : html
|
48
|
-
end
|
49
|
-
|
50
|
-
# Invisible reCAPTCHA implementation
|
51
|
-
def invisible_recaptcha_tags(options = {})
|
52
|
-
options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge options
|
53
|
-
text = options.delete(:text)
|
54
|
-
html, tag_attributes = Recaptcha::ClientHelper.recaptcha_components(options)
|
55
|
-
html << recaptcha_default_callback(options) if recaptcha_default_callback_required?(options)
|
56
|
-
case options[:ui]
|
57
|
-
when :button
|
58
|
-
html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
|
59
|
-
when :invisible
|
60
|
-
html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
|
61
|
-
else
|
62
|
-
raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
|
63
|
-
end
|
64
|
-
html.respond_to?(:html_safe) ? html.html_safe : html
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.recaptcha_components(options = {})
|
68
|
-
html = ''.dup
|
69
|
-
attributes = {}
|
70
|
-
fallback_uri = ''.dup
|
71
|
-
|
72
|
-
# Since leftover options get passed directly through as tag
|
73
|
-
# attributes, we must unconditionally delete all our options
|
74
|
-
options = options.dup
|
75
|
-
env = options.delete(:env)
|
76
|
-
class_attribute = options.delete(:class)
|
77
|
-
site_key = options.delete(:site_key)
|
78
|
-
hl = options.delete(:hl).to_s
|
79
|
-
nonce = options.delete(:nonce)
|
80
|
-
skip_script = (options.delete(:script) == false)
|
81
|
-
data_attributes = {}
|
82
|
-
[:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size, :tabindex].each do |data_attribute|
|
83
|
-
value = options.delete(data_attribute)
|
84
|
-
data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
|
85
|
-
end
|
86
|
-
|
87
|
-
unless Recaptcha::Verify.skip?(env)
|
88
|
-
site_key ||= Recaptcha.configuration.site_key!
|
89
|
-
script_url = Recaptcha.configuration.api_server_url
|
90
|
-
script_url += "?hl=#{hl}" unless hl == ""
|
91
|
-
nonce_attr = " nonce='#{nonce}'" if nonce
|
92
|
-
html << %(<script src="#{script_url}" async defer#{nonce_attr}></script>\n) unless skip_script
|
93
|
-
fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
|
94
|
-
attributes["data-sitekey"] = site_key
|
95
|
-
attributes.merge! data_attributes
|
96
|
-
end
|
97
|
-
|
98
|
-
# Append whatever that's left of options to be attributes on the tag.
|
99
|
-
attributes["class"] = "g-recaptcha #{class_attribute}"
|
100
|
-
tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
|
101
|
-
|
102
|
-
[html, tag_attributes, fallback_uri]
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
def recaptcha_default_callback(options = {})
|
108
|
-
nonce = options[:nonce]
|
109
|
-
nonce_attr = " nonce='#{nonce}'" if nonce
|
110
|
-
|
111
|
-
<<-HTML
|
112
|
-
<script#{nonce_attr}>
|
113
|
-
var invisibleRecaptchaSubmit = function () {
|
114
|
-
var closestForm = function (ele) {
|
115
|
-
var curEle = ele.parentNode;
|
116
|
-
while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
|
117
|
-
curEle = curEle.parentNode;
|
118
|
-
}
|
119
|
-
return curEle.nodeName === 'FORM' ? curEle : null
|
120
|
-
};
|
121
|
-
|
122
|
-
var eles = document.getElementsByClassName('g-recaptcha');
|
123
|
-
if (eles.length > 0) {
|
124
|
-
var form = closestForm(eles[0]);
|
125
|
-
if (form) {
|
126
|
-
form.submit();
|
127
|
-
}
|
128
|
-
}
|
129
|
-
};
|
130
|
-
</script>
|
131
|
-
HTML
|
132
|
-
end
|
133
|
-
|
134
|
-
def recaptcha_default_callback_required?(options)
|
135
|
-
options[:callback] == 'invisibleRecaptchaSubmit' &&
|
136
|
-
!Recaptcha::Verify.skip?(options[:env]) &&
|
137
|
-
options[:script] != false
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
data/lib/recaptcha/verify.rb
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Recaptcha
|
6
|
-
module Verify
|
7
|
-
# Your private API can be specified in the +options+ hash or preferably
|
8
|
-
# using the Configuration.
|
9
|
-
def verify_recaptcha(options = {})
|
10
|
-
options = {model: options} unless options.is_a? Hash
|
11
|
-
return true if Recaptcha::Verify.skip?(options[:env])
|
12
|
-
|
13
|
-
model = options[:model]
|
14
|
-
attribute = options[:attribute] || :base
|
15
|
-
recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
|
16
|
-
|
17
|
-
begin
|
18
|
-
verified = if recaptcha_response.empty?
|
19
|
-
false
|
20
|
-
else
|
21
|
-
recaptcha_verify_via_api_call(request, recaptcha_response, options)
|
22
|
-
end
|
23
|
-
|
24
|
-
if verified
|
25
|
-
flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
|
26
|
-
true
|
27
|
-
else
|
28
|
-
recaptcha_error(
|
29
|
-
model,
|
30
|
-
attribute,
|
31
|
-
options[:message],
|
32
|
-
"recaptcha.errors.verification_failed",
|
33
|
-
"reCAPTCHA verification failed, please try again."
|
34
|
-
)
|
35
|
-
false
|
36
|
-
end
|
37
|
-
rescue Timeout::Error
|
38
|
-
if Recaptcha.configuration.handle_timeouts_gracefully
|
39
|
-
recaptcha_error(
|
40
|
-
model,
|
41
|
-
attribute,
|
42
|
-
options[:message],
|
43
|
-
"recaptcha.errors.recaptcha_unreachable",
|
44
|
-
"Oops, we failed to validate your reCAPTCHA response. Please try again."
|
45
|
-
)
|
46
|
-
false
|
47
|
-
else
|
48
|
-
raise RecaptchaError, "Recaptcha unreachable."
|
49
|
-
end
|
50
|
-
rescue StandardError => e
|
51
|
-
raise RecaptchaError, e.message, e.backtrace
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def verify_recaptcha!(options = {})
|
56
|
-
verify_recaptcha(options) || raise(VerifyError)
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.skip?(env)
|
60
|
-
env ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
|
61
|
-
Recaptcha.configuration.skip_verify_env.include? env
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def recaptcha_verify_via_api_call(request, recaptcha_response, options)
|
67
|
-
secret_key = options[:secret_key] || Recaptcha.configuration.secret_key!
|
68
|
-
|
69
|
-
verify_hash = {
|
70
|
-
"secret" => secret_key,
|
71
|
-
"response" => recaptcha_response
|
72
|
-
}
|
73
|
-
|
74
|
-
unless options[:skip_remote_ip]
|
75
|
-
remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
|
76
|
-
verify_hash["remoteip"] = remoteip.to_s
|
77
|
-
end
|
78
|
-
|
79
|
-
reply = JSON.parse(Recaptcha.get(verify_hash, options))
|
80
|
-
reply['success'].to_s == "true" &&
|
81
|
-
recaptcha_hostname_valid?(reply['hostname'], options[:hostname])
|
82
|
-
end
|
83
|
-
|
84
|
-
def recaptcha_hostname_valid?(hostname, validation)
|
85
|
-
validation ||= Recaptcha.configuration.hostname
|
86
|
-
|
87
|
-
case validation
|
88
|
-
when nil, FalseClass then true
|
89
|
-
when String then validation == hostname
|
90
|
-
else validation.call(hostname)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def recaptcha_error(model, attribute, message, key, default)
|
95
|
-
message ||= Recaptcha.i18n(key, default)
|
96
|
-
if model
|
97
|
-
model.errors.add attribute, message
|
98
|
-
else
|
99
|
-
flash[:recaptcha_error] = message if recaptcha_flash_supported?
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def recaptcha_flash_supported?
|
104
|
-
request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|