recaptcha 2.3.0 → 5.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/recaptcha.rb CHANGED
@@ -1,20 +1,26 @@
1
- require 'recaptcha/configuration'
2
- require 'recaptcha/client_helper'
3
- require 'recaptcha/verify'
4
- require 'uri'
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
5
4
  require 'net/http'
5
+ require 'uri'
6
+
7
+ require 'recaptcha/configuration'
8
+ require 'recaptcha/helpers'
9
+ require 'recaptcha/adapters/controller_methods'
10
+ require 'recaptcha/adapters/view_methods'
11
+ if defined?(Rails)
12
+ require 'recaptcha/railtie'
13
+ end
6
14
 
7
15
  module Recaptcha
8
- CONFIG = {
9
- 'server_url' => '//www.google.com/recaptcha/api.js',
10
- 'secure_server_url' => 'https://www.google.com/recaptcha/api.js',
11
- 'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
12
- }
13
-
14
- USE_SSL_BY_DEFAULT = false
15
- HANDLE_TIMEOUTS_GRACEFULLY = true
16
- SKIP_VERIFY_ENV = ['test', 'cucumber']
17
16
  DEFAULT_TIMEOUT = 3
17
+ RESPONSE_LIMIT = 4000
18
+
19
+ class RecaptchaError < StandardError
20
+ end
21
+
22
+ class VerifyError < RecaptchaError
23
+ end
18
24
 
19
25
  # Gives access to the current Configuration.
20
26
  def self.configuration
@@ -45,39 +51,123 @@ module Recaptcha
45
51
  original_config.each { |key, value| configuration.send("#{key}=", value) }
46
52
  end
47
53
 
48
- def self.get(verify_hash, options)
49
- http = if Recaptcha.configuration.proxy
50
- proxy_server = URI.parse(Recaptcha.configuration.proxy)
51
- Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
54
+ def self.skip_env?(env)
55
+ configuration.skip_verify_env.include?(env || configuration.default_env)
56
+ end
57
+
58
+ def self.invalid_response?(resp)
59
+ resp.empty? || resp.length > RESPONSE_LIMIT
60
+ end
61
+
62
+ def self.verify_via_api_call(response, options)
63
+ if Recaptcha.configuration.enterprise
64
+ verify_via_api_call_enterprise(response, options)
52
65
  else
53
- Net::HTTP
66
+ verify_via_api_call_free(response, options)
54
67
  end
55
- query = URI.encode_www_form(verify_hash)
56
- uri = URI.parse(Recaptcha.configuration.verify_url + '?' + query)
57
- http_instance = http.new(uri.host, uri.port)
58
- http_instance.read_timeout = http_instance.open_timeout = options[:timeout] || DEFAULT_TIMEOUT
59
- if uri.port == 443
60
- http_instance.use_ssl = true
61
- if Recaptcha.configuration.disable_ssl_verification
62
- http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
63
- 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
64
92
  end
65
- request = Net::HTTP::Get.new(uri.request_uri)
66
- http_instance.request(request).body
67
93
  end
68
94
 
69
- def self.i18n(key, default)
70
- if defined?(I18n)
71
- I18n.translate(key, default: default)
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
+
106
+ if options[:with_reply] == true
107
+ return success, reply
72
108
  else
73
- default
109
+ return success
74
110
  end
75
111
  end
76
112
 
113
+ def self.hostname_valid?(hostname, validation)
114
+ validation ||= configuration.hostname
77
115
 
78
- class RecaptchaError < StandardError
116
+ case validation
117
+ when nil, FalseClass then true
118
+ when String then validation == hostname
119
+ else validation.call(hostname)
120
+ end
79
121
  end
80
122
 
81
- class VerifyError < RecaptchaError
123
+ def self.action_valid?(action, expected_action)
124
+ case expected_action
125
+ when nil, FalseClass then true
126
+ else action == expected_action
127
+ end
128
+ end
129
+
130
+ # Returns true iff score is greater or equal to (>=) minimum_score, or if no minimum_score was specified
131
+ def self.score_above_threshold?(score, minimum_score)
132
+ return true if minimum_score.nil?
133
+ return false if score.nil?
134
+
135
+ case minimum_score
136
+ when nil, FalseClass then true
137
+ else score >= minimum_score
138
+ end
139
+ end
140
+
141
+ def self.http_client_for(uri:, timeout: nil)
142
+ timeout ||= DEFAULT_TIMEOUT
143
+ http = if configuration.proxy
144
+ proxy_server = URI.parse(configuration.proxy)
145
+ Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
146
+ else
147
+ Net::HTTP
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)
157
+ query = URI.encode_www_form(verify_hash)
158
+ uri = URI.parse(configuration.verify_url + '?' + query)
159
+ http_instance = http_client_for(uri: uri, timeout: timeout)
160
+ request = Net::HTTP::Get.new(uri.request_uri)
161
+ JSON.parse(http_instance.request(request).body)
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)
82
172
  end
83
173
  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.
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: 2.3.0
4
+ version: 5.8.1
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: 2016-05-25 00:00:00.000000000 Z
11
+ date: 2021-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: activesupport
56
+ name: i18n
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: i18n
70
+ name: maxitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: maxitest
84
+ name: pry-byebug
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: pry-byebug
98
+ name: bump
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: bump
112
+ name: webmock
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: webmock
126
+ name: rubocop
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -147,15 +147,20 @@ files:
147
147
  - LICENSE
148
148
  - README.md
149
149
  - lib/recaptcha.rb
150
- - lib/recaptcha/client_helper.rb
150
+ - lib/recaptcha/adapters/controller_methods.rb
151
+ - lib/recaptcha/adapters/view_methods.rb
151
152
  - lib/recaptcha/configuration.rb
153
+ - lib/recaptcha/helpers.rb
152
154
  - lib/recaptcha/rails.rb
153
- - lib/recaptcha/verify.rb
155
+ - lib/recaptcha/railtie.rb
154
156
  - lib/recaptcha/version.rb
157
+ - rails/locales/en.yml
158
+ - rails/locales/fr.yml
155
159
  homepage: http://github.com/ambethia/recaptcha
156
160
  licenses:
157
161
  - MIT
158
- metadata: {}
162
+ metadata:
163
+ source_code_uri: https://github.com/ambethia/recaptcha
159
164
  post_install_message:
160
165
  rdoc_options: []
161
166
  require_paths:
@@ -164,15 +169,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
169
  requirements:
165
170
  - - ">="
166
171
  - !ruby/object:Gem::Version
167
- version: 2.0.0
172
+ version: 2.4.0
168
173
  required_rubygems_version: !ruby/object:Gem::Requirement
169
174
  requirements:
170
175
  - - ">="
171
176
  - !ruby/object:Gem::Version
172
177
  version: '0'
173
178
  requirements: []
174
- rubyforge_project:
175
- rubygems_version: 2.4.5.1
179
+ rubygems_version: 3.2.16
176
180
  signing_key:
177
181
  specification_version: 4
178
182
  summary: Helpers for the reCAPTCHA API
@@ -1,52 +0,0 @@
1
- module Recaptcha
2
- module ClientHelper
3
- # Your public API can be specified in the +options+ hash or preferably
4
- # using the Configuration.
5
- def recaptcha_tags(options = {})
6
- raise(RecaptchaError, "Secure Token is deprecated.") if options[:stoken]
7
- public_key = options[:public_key] || Recaptcha.configuration.public_key!
8
-
9
- script_url = Recaptcha.configuration.api_server_url(ssl: options[:ssl])
10
- script_url += "?hl=#{options[:hl]}" unless options[:hl].to_s == ""
11
- fallback_uri = "#{script_url.chomp('.js')}/fallback?k=#{public_key}"
12
-
13
- data_attributes = [:theme, :type, :callback, :expired_callback, :size]
14
- data_attributes = options.each_with_object({}) do |(k, v), a|
15
- a[k] = v if data_attributes.include?(k)
16
- end
17
- data_attributes[:sitekey] = public_key
18
- data_attributes = data_attributes.map { |k,v| %{data-#{k.to_s.tr('_','-')}="#{v}"} }.join(" ")
19
-
20
- html = %{<script src="#{script_url}" async defer></script>\n}
21
- html << %{<div class="g-recaptcha" #{data_attributes}></div>\n}
22
-
23
- if options[:noscript] != false
24
- html << <<-HTML
25
- <noscript>
26
- <div style="width: 302px; height: 352px;">
27
- <div style="width: 302px; height: 352px; position: relative;">
28
- <div style="width: 302px; height: 352px; position: absolute;">
29
- <iframe
30
- src="#{fallback_uri}"
31
- frameborder="0" scrolling="no"
32
- style="width: 302px; height:352px; border-style: none;">
33
- </iframe>
34
- </div>
35
- <div style="width: 250px; height: 80px; position: absolute; border-style: none;
36
- bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
37
- <textarea id="g-recaptcha-response" name="g-recaptcha-response"
38
- class="g-recaptcha-response"
39
- style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
40
- margin: 0px; padding: 0px; resize: none;" value="">
41
- </textarea>
42
- </div>
43
- </div>
44
- </div>
45
- </noscript>
46
- HTML
47
- end
48
-
49
- html.respond_to?(:html_safe) ? html.html_safe : html
50
- end
51
- end
52
- end
@@ -1,102 +0,0 @@
1
- require 'json'
2
-
3
- module Recaptcha
4
- module Verify
5
- # Your private API can be specified in the +options+ hash or preferably
6
- # using the Configuration.
7
- def verify_recaptcha(options = {})
8
- options = {:model => options} unless options.is_a? Hash
9
- return true if Recaptcha::Verify.skip?(options[:env])
10
-
11
- model = options[:model]
12
- attribute = options[:attribute] || :base
13
- recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
14
-
15
- begin
16
- verified = if recaptcha_response.empty?
17
- false
18
- else
19
- recaptcha_verify_via_api_call(request, recaptcha_response, options)
20
- end
21
-
22
- if verified
23
- flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
24
- true
25
- else
26
- recaptcha_error(
27
- model,
28
- attribute,
29
- options[:message],
30
- "recaptcha.errors.verification_failed",
31
- "reCAPTCHA verification failed, please try again."
32
- )
33
- false
34
- end
35
- rescue Timeout::Error
36
- if Recaptcha.configuration.handle_timeouts_gracefully
37
- recaptcha_error(
38
- model,
39
- attribute,
40
- options[:message],
41
- "recaptcha.errors.recaptcha_unreachable",
42
- "Oops, we failed to validate your reCAPTCHA response. Please try again."
43
- )
44
- false
45
- else
46
- raise RecaptchaError, "Recaptcha unreachable."
47
- end
48
- rescue StandardError => e
49
- raise RecaptchaError, e.message, e.backtrace
50
- end
51
- end
52
-
53
- def verify_recaptcha!(options = {})
54
- verify_recaptcha(options) or raise VerifyError
55
- end
56
-
57
- private
58
-
59
- def recaptcha_verify_via_api_call(request, recaptcha_response, options)
60
- private_key = options[:private_key] || Recaptcha.configuration.private_key!
61
- remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
62
-
63
- verify_hash = {
64
- "secret" => private_key,
65
- "remoteip" => remote_ip.to_s,
66
- "response" => recaptcha_response
67
- }
68
-
69
- reply = JSON.parse(Recaptcha.get(verify_hash, options))
70
- reply['success'].to_s == "true" &&
71
- recaptcha_hostname_valid?(reply['hostname'], options[:hostname])
72
- end
73
-
74
- def recaptcha_hostname_valid?(hostname, validation)
75
- validation ||= Recaptcha.configuration.hostname
76
-
77
- case validation
78
- when nil, FalseClass then true
79
- when String then validation == hostname
80
- else validation.call(hostname)
81
- end
82
- end
83
-
84
- def recaptcha_error(model, attribute, message, key, default)
85
- message = message || Recaptcha.i18n(key, default)
86
- if model
87
- model.errors.add attribute, message
88
- else
89
- flash[:recaptcha_error] = message if recaptcha_flash_supported?
90
- end
91
- end
92
-
93
- def recaptcha_flash_supported?
94
- request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
95
- end
96
-
97
- def self.skip?(env)
98
- env ||= ENV['RACK_ENV'] || ENV['RAILS_ENV'] || (Rails.env if defined? Rails.env)
99
- Recaptcha.configuration.skip_verify_env.include? env
100
- end
101
- end
102
- end