recaptcha 4.13.1 → 5.12.3

Sign up to get free protection for your applications and to get access to all the features.
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,123 @@ 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
+ score = reply.dig('riskAnalysis', 'score')
81
+ token_properties = reply['tokenProperties']
82
+ success = !token_properties.nil? &&
83
+ token_properties['valid'].to_s == 'true' &&
84
+ hostname_valid?(token_properties['hostname'], options[:hostname]) &&
85
+ action_valid?(token_properties['action'], options[:action]) &&
86
+ score_above_threshold?(score, options[:minimum_score]) &&
87
+ score_below_threshold?(score, options[:maximum_score])
88
+
89
+ if options[:with_reply] == true
90
+ [success, reply]
72
91
  else
73
- default
92
+ success
74
93
  end
75
94
  end
76
95
 
77
- class RecaptchaError < StandardError
96
+ def self.verify_via_api_call_free(response, options)
97
+ secret_key = options.fetch(:secret_key) { configuration.secret_key! }
98
+ verify_hash = { 'secret' => secret_key, 'response' => response }
99
+ verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
100
+
101
+ reply = api_verification_free(verify_hash, timeout: options[:timeout])
102
+ success = reply['success'].to_s == 'true' &&
103
+ hostname_valid?(reply['hostname'], options[:hostname]) &&
104
+ action_valid?(reply['action'], options[:action]) &&
105
+ score_above_threshold?(reply['score'], options[:minimum_score]) &&
106
+ score_below_threshold?(reply['score'], options[:maximum_score])
107
+
108
+ if options[:with_reply] == true
109
+ [success, reply]
110
+ else
111
+ success
112
+ end
78
113
  end
79
114
 
80
- class VerifyError < RecaptchaError
115
+ def self.hostname_valid?(hostname, validation)
116
+ validation ||= configuration.hostname
117
+
118
+ case validation
119
+ when nil, FalseClass then true
120
+ when String then validation == hostname
121
+ else validation.call(hostname)
122
+ end
123
+ end
124
+
125
+ def self.action_valid?(action, expected_action)
126
+ case expected_action
127
+ when nil, FalseClass then true
128
+ else action == expected_action
129
+ end
130
+ end
131
+
132
+ def self.score_above_threshold?(score, minimum_score)
133
+ !minimum_score || (score && score >= minimum_score)
134
+ end
135
+
136
+ def self.score_below_threshold?(score, maximum_score)
137
+ !maximum_score || (score && score <= maximum_score)
138
+ end
139
+
140
+ def self.http_client_for(uri:, timeout: nil)
141
+ timeout ||= DEFAULT_TIMEOUT
142
+ http = if configuration.proxy
143
+ proxy_server = URI.parse(configuration.proxy)
144
+ Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
145
+ else
146
+ Net::HTTP
147
+ end
148
+ instance = http.new(uri.host, uri.port)
149
+ instance.read_timeout = instance.open_timeout = timeout
150
+ instance.use_ssl = true if uri.port == 443
151
+
152
+ instance
153
+ end
154
+
155
+ def self.api_verification_free(verify_hash, timeout: nil)
156
+ query = URI.encode_www_form(verify_hash)
157
+ uri = URI.parse("#{configuration.verify_url}?#{query}")
158
+ http_instance = http_client_for(uri: uri, timeout: timeout)
159
+ request = Net::HTTP::Get.new(uri.request_uri)
160
+ JSON.parse(http_instance.request(request).body)
161
+ end
162
+
163
+ def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
164
+ query = URI.encode_www_form(query_params)
165
+ uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
166
+ http_instance = http_client_for(uri: uri, timeout: timeout)
167
+ request = Net::HTTP::Post.new(uri.request_uri)
168
+ request['Content-Type'] = 'application/json; charset=utf-8'
169
+ request.body = JSON.generate(body)
170
+ JSON.parse(http_instance.request(request).body)
81
171
  end
82
172
  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
+ ja:
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.13.1
4
+ version: 5.12.3
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-12-01 00:00:00.000000000 Z
11
+ date: 2022-09-05 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,157 +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
- when :input
62
- html << %(<input type="submit" #{tag_attributes} value="#{text}"/>\n)
63
- else
64
- raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
65
- end
66
- html.respond_to?(:html_safe) ? html.html_safe : html
67
- end
68
-
69
- def self.recaptcha_components(options = {})
70
- html = +''
71
- attributes = {}
72
- fallback_uri = +''
73
-
74
- # Since leftover options get passed directly through as tag
75
- # attributes, we must unconditionally delete all our options
76
- options = options.dup
77
- env = options.delete(:env)
78
- class_attribute = options.delete(:class)
79
- site_key = options.delete(:site_key)
80
- hl = options.delete(:hl)
81
- onload = options.delete(:onload)
82
- render = options.delete(:render)
83
- nonce = options.delete(:nonce)
84
- skip_script = (options.delete(:script) == false)
85
- ui = options.delete(:ui)
86
-
87
- data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
88
- data_attribute_keys << :tabindex unless ui == :button
89
- data_attributes = {}
90
- data_attribute_keys.each do |data_attribute|
91
- value = options.delete(data_attribute)
92
- data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
93
- end
94
-
95
- unless Recaptcha::Verify.skip?(env)
96
- site_key ||= Recaptcha.configuration.site_key!
97
- script_url = Recaptcha.configuration.api_server_url
98
- query_params = hash_to_query(
99
- hl: hl,
100
- onload: onload,
101
- render: render
102
- )
103
- script_url += "?#{query_params}" unless query_params.empty?
104
- nonce_attr = " nonce='#{nonce}'" if nonce
105
- html << %(<script src="#{script_url}" async defer#{nonce_attr}></script>\n) unless skip_script
106
- fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
107
- attributes["data-sitekey"] = site_key
108
- attributes.merge! data_attributes
109
- end
110
-
111
- # Append whatever that's left of options to be attributes on the tag.
112
- attributes["class"] = "g-recaptcha #{class_attribute}"
113
- tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
114
-
115
- [html, tag_attributes, fallback_uri]
116
- end
117
-
118
- private
119
-
120
- def recaptcha_default_callback(options = {})
121
- nonce = options[:nonce]
122
- nonce_attr = " nonce='#{nonce}'" if nonce
123
-
124
- <<-HTML
125
- <script#{nonce_attr}>
126
- var invisibleRecaptchaSubmit = function () {
127
- var closestForm = function (ele) {
128
- var curEle = ele.parentNode;
129
- while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
130
- curEle = curEle.parentNode;
131
- }
132
- return curEle.nodeName === 'FORM' ? curEle : null
133
- };
134
-
135
- var eles = document.getElementsByClassName('g-recaptcha');
136
- if (eles.length > 0) {
137
- var form = closestForm(eles[0]);
138
- if (form) {
139
- form.submit();
140
- }
141
- }
142
- };
143
- </script>
144
- HTML
145
- end
146
-
147
- def recaptcha_default_callback_required?(options)
148
- options[:callback] == 'invisibleRecaptchaSubmit' &&
149
- !Recaptcha::Verify.skip?(options[:env]) &&
150
- options[:script] != false
151
- end
152
-
153
- private_class_method def self.hash_to_query(hash)
154
- hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
155
- end
156
- end
157
- 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