recaptcha 5.7.0 → 5.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec76ff47d12ef3388c2a45d56850272fff3675c9f59619a09b55c918f70b5494
4
- data.tar.gz: 697f0a4d41ffab63c6eb5b7d7b81c1f5ac2b1305f27bb84b5fccc421bcd38e2c
3
+ metadata.gz: 555ad69df9bcc4154972887c8df613e687e2370caf4c4aa9e8e4843e02effd30
4
+ data.tar.gz: 40675afb3efbe1bdc78e48250715a8dd00bd1f4b27419f8bc72fbc1fd3911904
5
5
  SHA512:
6
- metadata.gz: 51b3627583241aef9a99e2a3ed9c78091c87a92e87210749d38e9ccf0d418dbec455fd524d83210b8331be7064efac2be2308366b8288c574c53b84f6013333e
7
- data.tar.gz: 8018c4668d7eef292e5908d609a7ebdd5cfbfb84b0c86a361dac36d3982cc075dc7e694c51639dd09eaf148cfd9536cecb12f6fa188ec72f87328e85976a4e22
6
+ metadata.gz: c8cc3cf0b1ccc9076a23a3f0588fcde888c131118089cb11fca59b79eccca511a61622286d087757b2a764744ac1370d3ab39e07a059dd5ab939e3c7d592660f
7
+ data.tar.gz: 8e716c170d96a0e39521d3c5a01a609c95dcdbe4daf5735936132e37bfc6ea8a7f73b9583bea653d145a950edaf3fdcbd1b6abdf63f886e04a6e12bbb052b7f1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## Next
2
2
 
3
+ ## 5.8.0
4
+ * Add support for the enterprise API
5
+
3
6
  ## 5.7.0
4
7
  * french locale
5
8
  * drop ruby 2.3
data/README.md CHANGED
@@ -68,6 +68,14 @@ export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
68
68
  export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
69
69
  ```
70
70
 
71
+ If you have an Enterprise API key:
72
+
73
+ ```shell
74
+ export RECAPTCHA_ENTERPRISE = 'true'
75
+ export RECAPTCHA_ENTERPRISE_API_KEY = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
76
+ export RECAPTCHA_ENTERPRISE_PROJECT_ID = 'my-project'
77
+ ```
78
+
71
79
  Add `recaptcha_tags` to the forms you want to protect:
72
80
 
73
81
  ```erb
@@ -159,16 +167,18 @@ you like.
159
167
 
160
168
  Some of the options available:
161
169
 
162
- | Option | Description |
163
- |----------------|-------------|
164
- | `:model` | Model to set errors.
165
- | `:attribute` | Model attribute to receive errors. (default: `:base`)
166
- | `:message` | Custom error message.
167
- | `:secret_key` | Override the secret API key from the configuration.
168
- | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
169
- | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
170
- | `:hostname` | Expected hostname or a callable that validates the hostname, see [domain validation](https://developers.google.com/recaptcha/docs/domain_validation) and [hostname](https://developers.google.com/recaptcha/docs/verify#api-response) docs. (default: `nil`, but can be changed by setting `config.hostname`)
171
- | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
170
+ | Option | Description |
171
+ |---------------------------|-------------|
172
+ | `:model` | Model to set errors.
173
+ | `:attribute` | Model attribute to receive errors. (default: `:base`)
174
+ | `:message` | Custom error message.
175
+ | `:secret_key` | Override the secret API key from the configuration.
176
+ | `:enterprise_api_key` | Override the Enterprise API key from the configuration.
177
+ | `:enterprise_project_id ` | Override the Enterprise project ID from the configuration.
178
+ | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
179
+ | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
180
+ | `:hostname` | Expected hostname or a callable that validates the hostname, see [domain validation](https://developers.google.com/recaptcha/docs/domain_validation) and [hostname](https://developers.google.com/recaptcha/docs/verify#api-response) docs. (default: `nil`, but can be changed by setting `config.hostname`)
181
+ | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
172
182
 
173
183
 
174
184
  ### `invisible_recaptcha_tags`
@@ -376,17 +386,19 @@ then you can either:
376
386
  2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
377
387
  don't have a use for the hidden input element.
378
388
 
379
- Note that you cannot submit/verify the same response token more than once or you will get a
380
- `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
381
- then you need to call `grecaptcha.execute(…)` again. This helper provides a JavaScript method (for
382
- each action) named `executeRecaptchaFor{action}` to make this easier. That is the same method that
383
- is invoked immediately. It simply calls `grecaptcha.execute` again and then calls the `callback`
384
- function with the response token.
389
+ Note that you cannot submit/verify the same response token more than once or you
390
+ will get a `timeout-or-duplicate` error code. If you need reset the captcha and
391
+ generate a new response token, then you need to call `grecaptcha.execute(…)` or
392
+ `grecaptcha.enterprise.execute(…)` again. This helper provides a JavaScript
393
+ method (for each action) named `executeRecaptchaFor{action}` to make this
394
+ easier. That is the same method that is invoked immediately. It simply calls
395
+ `grecaptcha.execute` or `grecaptcha.enterprise.execute` again and then calls the
396
+ `callback` function with the response token.
385
397
 
386
398
  You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
387
399
  response token and verifying it. This can easily happen with large forms that take the user a couple
388
400
  minutes to complete. Unlike v2, where you can use the `expired-callback` to be notified when the
389
- response expries, v3 appears to provide no such callback. See also
401
+ response expires, v3 appears to provide no such callback. See also
390
402
  [1](https://github.com/google/recaptcha/issues/281) and
391
403
  [2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
392
404
 
@@ -446,7 +458,7 @@ According to https://developers.google.com/recaptcha/docs/v3#placement,
446
458
 
447
459
  > Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
448
460
 
449
- You will need to verify each action individually with separate call to `verify_recaptcha`.
461
+ You will need to verify each action individually with a separate call to `verify_recaptcha`.
450
462
 
451
463
  ```ruby
452
464
  result_a = verify_recaptcha(action: 'a')
@@ -506,14 +518,20 @@ Recaptcha.configuration.skip_verify_env.delete("test")
506
518
  Recaptcha.configure do |config|
507
519
  config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
508
520
  config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
521
+
509
522
  # Uncomment the following line if you are using a proxy server:
510
523
  # config.proxy = 'http://myproxy.com.au:8080'
524
+
525
+ # Uncomment the following lines if you are using the Enterprise API:
526
+ # config.enterprise = true
527
+ # config.enterprise_api_key = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
528
+ # config.enterprise_project_id = 'my-project'
511
529
  end
512
530
  ```
513
531
 
514
532
  ### Recaptcha.with_configuration
515
533
 
516
- For temporary overwrites (not thread safe).
534
+ For temporary overwrites (not thread-safe).
517
535
 
518
536
  ```ruby
519
537
  Recaptcha.with_configuration(site_key: '12345') do
data/lib/recaptcha.rb CHANGED
@@ -60,11 +60,44 @@ module Recaptcha
60
60
  end
61
61
 
62
62
  def self.verify_via_api_call(response, options)
63
+ if Recaptcha.configuration.enterprise
64
+ verify_via_api_call_enterprise(response, options)
65
+ else
66
+ verify_via_api_call_free(response, options)
67
+ end
68
+ end
69
+
70
+ def self.verify_via_api_call_enterprise(response, options)
71
+ site_key = options.fetch(:site_key) { configuration.site_key! }
72
+ api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! }
73
+ project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! }
74
+
75
+ query_params = { 'key' => api_key }
76
+ body = { 'event' => { 'token' => response, 'siteKey' => site_key } }
77
+ body['event']['expectedAction'] = options[:action] if options.key?(:action)
78
+ body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
79
+
80
+ reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
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?(reply['score'], options[:minimum_score])
87
+
88
+ if options[:with_reply] == true
89
+ return success, reply
90
+ else
91
+ return success
92
+ end
93
+ end
94
+
95
+ def self.verify_via_api_call_free(response, options)
63
96
  secret_key = options.fetch(:secret_key) { configuration.secret_key! }
64
97
  verify_hash = { 'secret' => secret_key, 'response' => response }
65
98
  verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
66
99
 
67
- reply = api_verification(verify_hash, timeout: options[:timeout])
100
+ reply = api_verification_free(verify_hash, timeout: options[:timeout])
68
101
  success = reply['success'].to_s == 'true' &&
69
102
  hostname_valid?(reply['hostname'], options[:hostname]) &&
70
103
  action_valid?(reply['action'], options[:action]) &&
@@ -105,7 +138,7 @@ module Recaptcha
105
138
  end
106
139
  end
107
140
 
108
- def self.api_verification(verify_hash, timeout: nil)
141
+ def self.http_client_for(uri:, timeout: nil)
109
142
  timeout ||= DEFAULT_TIMEOUT
110
143
  http = if configuration.proxy
111
144
  proxy_server = URI.parse(configuration.proxy)
@@ -113,12 +146,28 @@ module Recaptcha
113
146
  else
114
147
  Net::HTTP
115
148
  end
149
+ instance = http.new(uri.host, uri.port)
150
+ instance.read_timeout = instance.open_timeout = timeout
151
+ instance.use_ssl = true if uri.port == 443
152
+
153
+ instance
154
+ end
155
+
156
+ def self.api_verification_free(verify_hash, timeout: nil)
116
157
  query = URI.encode_www_form(verify_hash)
117
158
  uri = URI.parse(configuration.verify_url + '?' + query)
118
- http_instance = http.new(uri.host, uri.port)
119
- http_instance.read_timeout = http_instance.open_timeout = timeout
120
- http_instance.use_ssl = true if uri.port == 443
159
+ http_instance = http_client_for(uri: uri, timeout: timeout)
121
160
  request = Net::HTTP::Get.new(uri.request_uri)
122
161
  JSON.parse(http_instance.request(request).body)
123
162
  end
163
+
164
+ def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
165
+ query = URI.encode_www_form(query_params)
166
+ uri = URI.parse(configuration.verify_url + "/#{project_id}/assessments" + '?' + query)
167
+ http_instance = http_client_for(uri: uri, timeout: timeout)
168
+ request = Net::HTTP::Post.new(uri.request_uri)
169
+ request['Content-Type'] = 'application/json; charset=utf-8'
170
+ request.body = JSON.generate(body)
171
+ JSON.parse(http_instance.request(request).body)
172
+ end
124
173
  end
@@ -31,11 +31,14 @@ module Recaptcha
31
31
  #
32
32
  class Configuration
33
33
  DEFAULTS = {
34
- 'server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
- 'verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify'
34
+ 'free_server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
+ 'enterprise_server_url' => 'https://www.recaptcha.net/recaptcha/enterprise.js',
36
+ 'free_verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify',
37
+ 'enterprise_verify_url' => 'https://recaptchaenterprise.googleapis.com/v1beta1/projects'
36
38
  }.freeze
37
39
 
38
- attr_accessor :default_env, :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
40
+ attr_accessor :default_env, :skip_verify_env, :proxy, :secret_key, :site_key, :handle_timeouts_gracefully, :hostname
41
+ attr_accessor :enterprise, :enterprise_api_key, :enterprise_project_id
39
42
  attr_writer :api_server_url, :verify_url
40
43
 
41
44
  def initialize #:nodoc:
@@ -45,6 +48,11 @@ module Recaptcha
45
48
 
46
49
  @secret_key = ENV['RECAPTCHA_SECRET_KEY']
47
50
  @site_key = ENV['RECAPTCHA_SITE_KEY']
51
+
52
+ @enterprise = ENV['RECAPTCHA_ENTERPRISE'] == 'true'
53
+ @enterprise_api_key = ENV['RECAPTCHA_ENTERPRISE_API_KEY']
54
+ @enterprise_project_id = ENV['RECAPTCHA_ENTERPRISE_PROJECT_ID']
55
+
48
56
  @verify_url = nil
49
57
  @api_server_url = nil
50
58
  end
@@ -57,12 +65,20 @@ module Recaptcha
57
65
  site_key || raise(RecaptchaError, "No site key specified.")
58
66
  end
59
67
 
68
+ def enterprise_api_key!
69
+ enterprise_api_key || raise(RecaptchaError, "No Enterprise API key specified.")
70
+ end
71
+
72
+ def enterprise_project_id!
73
+ enterprise_project_id || raise(RecaptchaError, "No Enterprise project ID specified.")
74
+ end
75
+
60
76
  def api_server_url
61
- @api_server_url || DEFAULTS.fetch('server_url')
77
+ @api_server_url || (enterprise ? DEFAULTS.fetch('enterprise_server_url') : DEFAULTS.fetch('free_server_url'))
62
78
  end
63
79
 
64
80
  def verify_url
65
- @verify_url || DEFAULTS.fetch('verify_url')
81
+ @verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url'))
66
82
  end
67
83
  end
68
84
  end
@@ -174,7 +174,8 @@ module Recaptcha
174
174
 
175
175
  # v3
176
176
 
177
- # Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and
177
+ # Renders a script that calls `grecaptcha.execute` or
178
+ # `grecaptcha.enterprise.execute` for the given `site_key` and `action` and
178
179
  # calls the `callback` with the resulting response token.
179
180
  private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
180
181
  nonce = options[:nonce]
@@ -185,8 +186,8 @@ module Recaptcha
185
186
  // Define function so that we can call it again later if we need to reset it
186
187
  // This executes reCAPTCHA and then calls our callback.
187
188
  function #{recaptcha_v3_execute_function_name(action)}() {
188
- grecaptcha.ready(function() {
189
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
189
+ #{recaptcha_ready_method_name}(function() {
190
+ #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
190
191
  #{callback}('#{id}', token)
191
192
  });
192
193
  });
@@ -199,8 +200,8 @@ module Recaptcha
199
200
  // Returns a Promise that resolves with the response token.
200
201
  async function #{recaptcha_v3_async_execute_function_name(action)}() {
201
202
  return new Promise((resolve, reject) => {
202
- grecaptcha.ready(async function() {
203
- resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'}))
203
+ #{recaptcha_ready_method_name}(async function() {
204
+ resolve(await #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}))
204
205
  });
205
206
  })
206
207
  };
@@ -217,8 +218,8 @@ module Recaptcha
217
218
  <<-HTML
218
219
  <script#{nonce_attr}>
219
220
  function #{recaptcha_v3_execute_function_name(action)}() {
220
- grecaptcha.ready(function() {
221
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
221
+ #{recaptcha_ready_method_name}(function() {
222
+ #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
222
223
  #{callback}('#{id}', token)
223
224
  });
224
225
  });
@@ -251,8 +252,9 @@ module Recaptcha
251
252
  recaptcha_v3_inline_script?(options)
252
253
  end
253
254
 
254
- # Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls
255
- # grecaptcha.execute). You can call it again later to reset it.
255
+ # Returns the name of the JavaScript function that actually executes the
256
+ # reCAPTCHA code (calls `grecaptcha.execute` or
257
+ # `grecaptcha.enterprise.execute`). You can call it again later to reset it.
256
258
  def self.recaptcha_v3_execute_function_name(action)
257
259
  "executeRecaptchaFor#{sanitize_action_for_js(action)}"
258
260
  end
@@ -296,6 +298,14 @@ module Recaptcha
296
298
  HTML
297
299
  end
298
300
 
301
+ def self.recaptcha_execute_method_name
302
+ Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.execute" : "grecaptcha.execute"
303
+ end
304
+
305
+ def self.recaptcha_ready_method_name
306
+ Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.ready" : "grecaptcha.ready"
307
+ end
308
+
299
309
  private_class_method def self.default_callback_required?(options)
300
310
  options[:callback] == 'invisibleRecaptchaSubmit' &&
301
311
  !Recaptcha.skip_env?(options[:env]) &&
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Recaptcha
4
- VERSION = '5.7.0'
4
+ VERSION = '5.8.0'
5
5
  end
data/rails/locales/fr.yml CHANGED
@@ -2,4 +2,4 @@ fr:
2
2
  recaptcha:
3
3
  errors:
4
4
  verification_failed: La vérification reCAPTCHA a échoué, veuillez essayer à nouveau.
5
- recaptcha_unreachable: Oops, nous n'avons pas vu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
5
+ recaptcha_unreachable: Oops, nous n'avons pas pu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
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: 5.7.0
4
+ version: 5.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason L Perry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-05 00:00:00.000000000 Z
11
+ date: 2021-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -176,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
176
  - !ruby/object:Gem::Version
177
177
  version: '0'
178
178
  requirements: []
179
- rubygems_version: 3.1.3
179
+ rubygems_version: 3.2.16
180
180
  signing_key:
181
181
  specification_version: 4
182
182
  summary: Helpers for the reCAPTCHA API