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.
data/lib/recaptcha.rb CHANGED
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'recaptcha/configuration'
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.get(verify_hash, options)
54
- http = if Recaptcha.configuration.proxy
55
- proxy_server = URI.parse(Recaptcha.configuration.proxy)
56
- Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
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
- Net::HTTP
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.i18n(key, default)
70
- if defined?(I18n)
71
- I18n.translate(key, default: default)
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
- default
91
+ success
74
92
  end
75
93
  end
76
94
 
77
- class RecaptchaError < StandardError
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
- class VerifyError < RecaptchaError
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
@@ -0,0 +1,5 @@
1
+ en:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: reCAPTCHA verification failed, please try again.
5
+ recaptcha_unreachable: Oops, we failed to validate your reCAPTCHA response. Please try again.
@@ -0,0 +1,5 @@
1
+ fr:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: La vérification reCAPTCHA a échoué, veuillez essayer à nouveau.
5
+ recaptcha_unreachable: Oops, nous n'avons pas pu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
@@ -0,0 +1,5 @@
1
+ en:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: reCAPTCHA認証に失敗しました。もう一度お試しください。
5
+ recaptcha_unreachable: reCAPTCHAのレスポンスを検証できませんでした。もう一度お試しください。
@@ -0,0 +1,5 @@
1
+ nl:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: reCAPTCHA-verificatie mislukt, probeer het opnieuw.
5
+ recaptcha_unreachable: Oeps, we hebben uw reCAPTCHA-antwoord niet kunnen valideren. Probeer het opnieuw.
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.11.0
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: 2018-08-05 00:00:00.000000000 Z
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/client_helper.rb
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
- post_install_message:
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.3.0
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
- rubyforge_project:
190
- rubygems_version: 2.7.6
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
@@ -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