recaptcha 1.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,21 +1,26 @@
1
- require 'recaptcha/configuration'
2
- require 'recaptcha/client_helper'
3
- require 'recaptcha/verify'
4
- require 'recaptcha/token'
5
- require 'uri'
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
6
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
7
14
 
8
15
  module Recaptcha
9
- CONFIG = {
10
- 'server_url' => '//www.google.com/recaptcha/api.js',
11
- 'secure_server_url' => 'https://www.google.com/recaptcha/api.js',
12
- 'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
13
- }
14
-
15
- USE_SSL_BY_DEFAULT = false
16
- HANDLE_TIMEOUTS_GRACEFULLY = true
17
- SKIP_VERIFY_ENV = ['test', 'cucumber']
18
16
  DEFAULT_TIMEOUT = 3
17
+ RESPONSE_LIMIT = 4000
18
+
19
+ class RecaptchaError < StandardError
20
+ end
21
+
22
+ class VerifyError < RecaptchaError
23
+ end
19
24
 
20
25
  # Gives access to the current Configuration.
21
26
  def self.configuration
@@ -41,43 +46,128 @@ module Recaptcha
41
46
  configuration.send("#{key}=", value)
42
47
  end
43
48
 
44
- result = yield if block_given?
45
-
49
+ yield if block_given?
50
+ ensure
46
51
  original_config.each { |key, value| configuration.send("#{key}=", value) }
47
- result
48
52
  end
49
53
 
50
- def self.get(verify_hash, options)
51
- http = if Recaptcha.configuration.proxy
52
- proxy_server = URI.parse(Recaptcha.configuration.proxy)
53
- 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)
54
65
  else
55
- Net::HTTP
66
+ verify_via_api_call_free(response, options)
56
67
  end
57
- query = URI.encode_www_form(verify_hash)
58
- uri = URI.parse(Recaptcha.configuration.verify_url + '?' + query)
59
- http_instance = http.new(uri.host, uri.port)
60
- http_instance.read_timeout = http_instance.open_timeout = options[:timeout] || DEFAULT_TIMEOUT
61
- if uri.port == 443
62
- http_instance.use_ssl = true
63
- http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
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: 1.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-04-08 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,16 +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/token.rb
154
- - lib/recaptcha/verify.rb
155
+ - lib/recaptcha/railtie.rb
155
156
  - lib/recaptcha/version.rb
157
+ - rails/locales/en.yml
158
+ - rails/locales/fr.yml
156
159
  homepage: http://github.com/ambethia/recaptcha
157
160
  licenses:
158
161
  - MIT
159
- metadata: {}
162
+ metadata:
163
+ source_code_uri: https://github.com/ambethia/recaptcha
160
164
  post_install_message:
161
165
  rdoc_options: []
162
166
  require_paths:
@@ -165,15 +169,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
169
  requirements:
166
170
  - - ">="
167
171
  - !ruby/object:Gem::Version
168
- version: 2.0.0
172
+ version: 2.4.0
169
173
  required_rubygems_version: !ruby/object:Gem::Requirement
170
174
  requirements:
171
175
  - - ">="
172
176
  - !ruby/object:Gem::Version
173
177
  version: '0'
174
178
  requirements: []
175
- rubyforge_project:
176
- rubygems_version: 2.4.5.1
179
+ rubygems_version: 3.2.16
177
180
  signing_key:
178
181
  specification_version: 4
179
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
- public_key = options[:public_key] || Recaptcha.configuration.public_key!
7
-
8
- script_url = Recaptcha.configuration.api_server_url(ssl: options[:ssl])
9
- script_url += "?hl=#{options[:hl]}" unless options[:hl].to_s == ""
10
- fallback_uri = "#{script_url.chomp('.js')}/fallback?k=#{public_key}"
11
-
12
- data_attributes = [:theme, :type, :callback, :expired_callback, :size]
13
- data_attributes = options.each_with_object({}) do |(k, v), a|
14
- a[k] = v if data_attributes.include?(k)
15
- end
16
- data_attributes[:sitekey] = public_key
17
- data_attributes[:stoken] = Recaptcha::Token.secure_token if options[:stoken] != false
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,24 +0,0 @@
1
- require 'json'
2
- require 'recaptcha'
3
- require 'base64'
4
- require 'securerandom'
5
- require 'openssl'
6
-
7
- module Recaptcha
8
- module Token
9
-
10
- def self.secure_token
11
- private_key = Recaptcha.configuration.private_key
12
- raise RecaptchaError, "No private key specified." unless private_key
13
-
14
- stoken_json = {'session_id' => SecureRandom.uuid, 'ts_ms' => (Time.now.to_f * 1000).to_i}.to_json
15
- cipher = OpenSSL::Cipher::AES128.new(:ECB)
16
- private_key_digest = Digest::SHA1.digest(private_key)[0...16]
17
-
18
- cipher.encrypt
19
- cipher.key = private_key_digest
20
- encrypted_stoken = cipher.update(stoken_json) << cipher.final
21
- Base64.urlsafe_encode64(encrypted_stoken).gsub(/\=+\Z/, '')
22
- end
23
- end
24
- end
@@ -1,93 +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
- model = options[:model]
10
- attribute = options[:attribute] || :base
11
-
12
- return true if Recaptcha::Verify.skip?(options[:env])
13
-
14
- private_key = options[:private_key] || Recaptcha.configuration.private_key!
15
- recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
16
-
17
- begin
18
- # env['REMOTE_ADDR'] to retrieve IP for Grape API
19
- remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
20
- verify_hash = {
21
- "secret" => private_key,
22
- "remoteip" => remote_ip.to_s,
23
- "response" => recaptcha_response
24
- }
25
-
26
- raw_reply = Recaptcha.get(verify_hash, options)
27
- reply = JSON.parse(raw_reply)
28
- answer = reply['success']
29
-
30
- if hostname_valid?(reply['hostname'], options[:hostname]) && answer.to_s == 'true'
31
- flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
32
- true
33
- else
34
- recaptcha_error(
35
- model,
36
- attribute,
37
- options[:message],
38
- "recaptcha.errors.verification_failed",
39
- "reCAPTCHA verification failed, please try again."
40
- )
41
- false
42
- end
43
- rescue Timeout::Error
44
- if Recaptcha.configuration.handle_timeouts_gracefully
45
- recaptcha_error(
46
- model,
47
- attribute,
48
- options[:message],
49
- "recaptcha.errors.recaptcha_unreachable",
50
- "Oops, we failed to validate your reCAPTCHA response. Please try again."
51
- )
52
- false
53
- else
54
- raise RecaptchaError, "Recaptcha unreachable."
55
- end
56
- rescue StandardError => e
57
- raise RecaptchaError, e.message, e.backtrace
58
- end
59
- end
60
-
61
- def verify_recaptcha!(options = {})
62
- verify_recaptcha(options) or raise VerifyError
63
- end
64
-
65
- private
66
-
67
- def hostname_valid?(hostname, validation)
68
- case validation
69
- when nil, FalseClass then true
70
- when String then validation == hostname
71
- else validation.call(hostname)
72
- end
73
- end
74
-
75
- def recaptcha_error(model, attribute, message, key, default)
76
- message = message || Recaptcha.i18n(key, default)
77
- if model
78
- model.errors.add attribute, message
79
- else
80
- flash[:recaptcha_error] = message if recaptcha_flash_supported?
81
- end
82
- end
83
-
84
- def recaptcha_flash_supported?
85
- request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
86
- end
87
-
88
- def self.skip?(env)
89
- env ||= ENV['RACK_ENV'] || ENV['RAILS_ENV'] || (Rails.env if defined? Rails.env)
90
- Recaptcha.configuration.skip_verify_env.include? env
91
- end
92
- end
93
- end