acts_as_textcaptcha 4.2.0 → 4.5.2
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +1165 -0
- data/.simplecov +11 -0
- data/.travis.yml +30 -8
- data/Appraisals +9 -6
- data/CHANGELOG.md +94 -34
- data/CODE_OF_CONDUCT.md +54 -31
- data/CONTRIBUTING.md +14 -9
- data/Gemfile +2 -2
- data/LICENSE +165 -0
- data/PULL_REQUEST_TEMPLATE.md +16 -0
- data/README.md +206 -228
- data/Rakefile +26 -19
- data/acts_as_textcaptcha.gemspec +57 -41
- data/bin/console +2 -5
- data/bin/setup +7 -0
- data/gemfiles/rails_3.gemfile +1 -0
- data/gemfiles/rails_4.gemfile +2 -1
- data/gemfiles/rails_5.gemfile +2 -1
- data/gemfiles/rails_6.gemfile +8 -0
- data/lib/acts_as_textcaptcha.rb +9 -4
- data/lib/acts_as_textcaptcha/errors.rb +21 -0
- data/lib/acts_as_textcaptcha/framework/rails.rb +3 -1
- data/lib/acts_as_textcaptcha/railtie.rb +9 -0
- data/lib/acts_as_textcaptcha/tasks/textcaptcha.rake +17 -0
- data/lib/acts_as_textcaptcha/textcaptcha.rb +75 -72
- data/lib/acts_as_textcaptcha/textcaptcha_api.rb +39 -45
- data/lib/acts_as_textcaptcha/textcaptcha_cache.rb +12 -16
- data/lib/acts_as_textcaptcha/textcaptcha_config.rb +47 -0
- data/lib/acts_as_textcaptcha/textcaptcha_helper.rb +13 -14
- data/lib/acts_as_textcaptcha/version.rb +3 -1
- metadata +68 -46
- data/.coveralls.yml +0 -1
- data/LICENSE.txt +0 -21
- data/config/textcaptcha.yml +0 -34
- data/lib/tasks/textcaptcha.rake +0 -21
- data/test/schema.rb +0 -34
- data/test/test_helper.rb +0 -44
- data/test/test_models.rb +0 -69
- data/test/textcaptcha_api_test.rb +0 -46
- data/test/textcaptcha_cache_test.rb +0 -25
- data/test/textcaptcha_helper_test.rb +0 -68
- data/test/textcaptcha_test.rb +0 -198
@@ -1,61 +1,55 @@
|
|
1
|
-
#
|
2
|
-
# loads and parses captcha question and answers
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
3
|
+
require "json"
|
5
4
|
|
6
5
|
module ActsAsTextcaptcha
|
7
|
-
|
8
|
-
# raised if an empty response is returned
|
9
|
-
class EmptyResponseError < StandardError; end;
|
10
|
-
|
11
6
|
class TextcaptchaApi
|
7
|
+
BASE_URL = "http://textcaptcha.com"
|
8
|
+
|
9
|
+
def initialize(api_key: nil, api_endpoint: nil, raise_errors: false)
|
10
|
+
self.uri = if api_endpoint
|
11
|
+
URI(api_endpoint)
|
12
|
+
else
|
13
|
+
URI("#{BASE_URL}/#{api_key}.json")
|
14
|
+
end
|
15
|
+
self.raise_errors = raise_errors || false
|
16
|
+
rescue URI::InvalidURIError => e
|
17
|
+
raise ApiKeyError.new(api_key, e)
|
18
|
+
end
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
def self.fetch(api_key, options = {})
|
16
|
-
begin
|
17
|
-
url = uri_parser.parse("#{ENDPOINT}#{api_key}")
|
18
|
-
http = Net::HTTP.new(url.host, url.port)
|
19
|
-
if options[:http_open_timeout]
|
20
|
-
http.open_timeout = options[:http_open_timeout]
|
21
|
-
end
|
22
|
-
if options[:http_read_timeout]
|
23
|
-
http.read_timeout = options[:http_read_timeout]
|
24
|
-
end
|
25
|
-
|
26
|
-
response = http.get(url.path)
|
27
|
-
if response.body.to_s.empty?
|
28
|
-
raise ActsAsTextcaptcha::EmptyResponseError
|
29
|
-
else
|
30
|
-
return parse(response.body)
|
31
|
-
end
|
32
|
-
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
|
33
|
-
Errno::EHOSTUNREACH, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
34
|
-
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
35
|
-
URI::InvalidURIError, ActsAsTextcaptcha::EmptyResponseError,
|
36
|
-
REXML::ParseException
|
37
|
-
# rescue from these errors and continue
|
38
|
-
end
|
20
|
+
def fetch
|
21
|
+
parse(get.to_s)
|
39
22
|
end
|
40
23
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_accessor :uri, :raise_errors
|
27
|
+
|
28
|
+
def get
|
29
|
+
response = Net::HTTP.new(uri.host, uri.port).get(uri.path)
|
30
|
+
if response.code == "200"
|
31
|
+
response.body
|
46
32
|
else
|
47
|
-
|
33
|
+
handle_error ResponseError.new(uri, "status: #{response.code}")
|
48
34
|
end
|
49
|
-
|
50
|
-
|
35
|
+
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
|
36
|
+
Errno::EHOSTUNREACH, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
37
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
38
|
+
Net::ProtocolError => e
|
39
|
+
handle_error ResponseError.new(uri, e)
|
51
40
|
end
|
52
41
|
|
42
|
+
def parse(response)
|
43
|
+
JSON.parse(response) unless response.empty?
|
44
|
+
rescue JSON::ParserError
|
45
|
+
handle_error ParseError.new(uri)
|
46
|
+
end
|
53
47
|
|
54
|
-
|
48
|
+
def handle_error(error)
|
49
|
+
raise error if raise_errors
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
51
|
+
Rails.logger.error("#{error.class} #{error.message}")
|
52
|
+
nil
|
59
53
|
end
|
60
54
|
end
|
61
55
|
end
|
@@ -1,34 +1,30 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A simple cache for storing Textcaptcha answers, Rails.cache is used as the
|
4
|
+
# backend (ActiveSupport::Cache). This must not be set as a `:null_store`.
|
4
5
|
|
5
6
|
module ActsAsTextcaptcha
|
6
7
|
class TextcaptchaCache
|
7
|
-
|
8
|
-
|
9
|
-
DEFAULT_CACHE_EXPIRY_MINUTES = 10
|
8
|
+
KEY_PREFIX = "acts_as_textcaptcha-"
|
9
|
+
DEFAULT_EXPIRY_MINUTES = 10
|
10
10
|
|
11
11
|
def write(key, value, options = {})
|
12
|
-
unless options.has_key?(:expires_in)
|
13
|
-
options[:expires_in] = DEFAULT_CACHE_EXPIRY_MINUTES.minutes
|
14
|
-
end
|
12
|
+
options[:expires_in] = DEFAULT_EXPIRY_MINUTES.minutes unless options.has_key?(:expires_in)
|
15
13
|
Rails.cache.write(cache_key(key), value, options)
|
16
14
|
end
|
17
15
|
|
18
|
-
def read(key
|
19
|
-
Rails.cache.read(cache_key(key)
|
16
|
+
def read(key)
|
17
|
+
Rails.cache.read(cache_key(key))
|
20
18
|
end
|
21
19
|
|
22
|
-
def delete(key
|
23
|
-
Rails.cache.delete(cache_key(key)
|
20
|
+
def delete(key)
|
21
|
+
Rails.cache.delete(cache_key(key))
|
24
22
|
end
|
25
23
|
|
26
24
|
private
|
27
25
|
|
28
|
-
# since this cache may be shared with other objects
|
29
|
-
# a prefix is used in all cache keys
|
30
26
|
def cache_key(key)
|
31
|
-
"#{
|
27
|
+
"#{KEY_PREFIX}#{key}"
|
32
28
|
end
|
33
29
|
end
|
34
30
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTextcaptcha
|
4
|
+
class TextcaptchaConfig
|
5
|
+
YAML = <<~CONFIG
|
6
|
+
development: &common_settings
|
7
|
+
api_key: 'TEXTCAPTCHA_API_IDENT' # see https://textcaptcha.com for details
|
8
|
+
# api_endpoint: nil # Optional API URL to fetch questions and answers from
|
9
|
+
# raise_errors: false # Optional flag, if true errors will be raised if the API endpoint fails
|
10
|
+
# cache_expiry_minutes: 10 # Optional minutes for captcha answers to persist in the cache (default 10 minutes)
|
11
|
+
|
12
|
+
questions:
|
13
|
+
- question: 'Is ice hot or cold?'
|
14
|
+
answers: 'cold'
|
15
|
+
- question: 'what color is an orange?'
|
16
|
+
answers: 'orange'
|
17
|
+
- question: 'what is two plus 3?'
|
18
|
+
answers: '5,five'
|
19
|
+
- question: 'what is 5 times two?'
|
20
|
+
answers: '10,ten'
|
21
|
+
- question: 'How many colors in the list, green, brown, foot and blue?'
|
22
|
+
answers: '3,three'
|
23
|
+
- question: 'what is Georges name?'
|
24
|
+
answers: 'george'
|
25
|
+
- question: '11 minus 1?'
|
26
|
+
answers: '10,ten'
|
27
|
+
- question: 'is boiling water hot or cold?'
|
28
|
+
answers: 'hot'
|
29
|
+
- question: 'what color is my blue shirt today?'
|
30
|
+
answers: 'blue'
|
31
|
+
- question: 'what is 16 plus 4?'
|
32
|
+
answers: '20,twenty'
|
33
|
+
|
34
|
+
test:
|
35
|
+
<<: *common_settings
|
36
|
+
api_key: 'TEST_TEXTCAPTCHA_API_IDENT'
|
37
|
+
|
38
|
+
production:
|
39
|
+
<<: *common_settings
|
40
|
+
CONFIG
|
41
|
+
|
42
|
+
def self.create(path: "./config/textcaptcha.yml")
|
43
|
+
FileUtils.mkdir_p(File.dirname(path))
|
44
|
+
File.open(path, "w") { |f| f.write(YAML) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,21 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsTextcaptcha
|
2
4
|
module TextcaptchaHelper
|
5
|
+
def textcaptcha_fields(form, &block)
|
6
|
+
build_textcaptcha_form_elements(form, &block) if form.object.perform_textcaptcha? && form.object.textcaptcha_key
|
7
|
+
end
|
3
8
|
|
4
|
-
|
5
|
-
def textcaptcha_fields(f, &block)
|
6
|
-
model = f.object
|
7
|
-
captcha_html = ''
|
8
|
-
if model.perform_textcaptcha?
|
9
|
-
if model.textcaptcha_key
|
10
|
-
captcha_html += f.hidden_field(:textcaptcha_key)
|
11
|
-
if model.textcaptcha_question
|
12
|
-
captcha_html += capture(&block)
|
13
|
-
elsif model.textcaptcha_answer
|
14
|
-
captcha_html += f.hidden_field(:textcaptcha_answer)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
9
|
+
private
|
18
10
|
|
11
|
+
def build_textcaptcha_form_elements(form, &block)
|
12
|
+
captcha_html = form.hidden_field(:textcaptcha_key)
|
13
|
+
if form.object.textcaptcha_question
|
14
|
+
captcha_html += capture(&block)
|
15
|
+
elsif form.object.textcaptcha_answer
|
16
|
+
captcha_html += form.hidden_field(:textcaptcha_answer)
|
17
|
+
end
|
19
18
|
captcha_html.html_safe
|
20
19
|
end
|
21
20
|
end
|
metadata
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_textcaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.2
|
4
|
+
version: 4.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Hutchinson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: pry-byebug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rubocop
|
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:
|
70
|
+
name: rdoc
|
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:
|
84
|
+
name: appraisal
|
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:
|
98
|
+
name: minitest
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,21 +109,35 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: rails
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - "
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: 6.0.3.4
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - "
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: 6.0.3.4
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.19.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.19.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sqlite3
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - ">="
|
@@ -137,7 +151,7 @@ dependencies:
|
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
154
|
+
name: webmock
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
142
156
|
requirements:
|
143
157
|
- - ">="
|
@@ -150,73 +164,81 @@ dependencies:
|
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
166
|
version: '0'
|
153
|
-
description:
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
167
|
+
description: |2
|
168
|
+
ActsAsTextcaptcha provides spam protection for Rails models with text-based
|
169
|
+
logic question captchas. Questions are fetched from Rob Tuley's
|
170
|
+
textcaptcha.com They can be solved easily by humans but are tough for robots
|
171
|
+
to crack.
|
158
172
|
email:
|
159
173
|
- matt@hiddenloop.com
|
160
174
|
executables: []
|
161
175
|
extensions: []
|
162
|
-
extra_rdoc_files:
|
176
|
+
extra_rdoc_files:
|
177
|
+
- README.md
|
178
|
+
- LICENSE
|
163
179
|
files:
|
164
|
-
- ".coveralls.yml"
|
165
180
|
- ".gitignore"
|
181
|
+
- ".rubocop.yml"
|
182
|
+
- ".simplecov"
|
166
183
|
- ".travis.yml"
|
167
184
|
- Appraisals
|
168
185
|
- CHANGELOG.md
|
169
186
|
- CODE_OF_CONDUCT.md
|
170
187
|
- CONTRIBUTING.md
|
171
188
|
- Gemfile
|
172
|
-
- LICENSE
|
189
|
+
- LICENSE
|
190
|
+
- PULL_REQUEST_TEMPLATE.md
|
173
191
|
- README.md
|
174
192
|
- Rakefile
|
175
193
|
- acts_as_textcaptcha.gemspec
|
176
194
|
- bin/console
|
177
|
-
-
|
195
|
+
- bin/setup
|
178
196
|
- gemfiles/rails_3.gemfile
|
179
197
|
- gemfiles/rails_4.gemfile
|
180
198
|
- gemfiles/rails_5.gemfile
|
199
|
+
- gemfiles/rails_6.gemfile
|
181
200
|
- lib/acts_as_textcaptcha.rb
|
201
|
+
- lib/acts_as_textcaptcha/errors.rb
|
182
202
|
- lib/acts_as_textcaptcha/framework/rails.rb
|
203
|
+
- lib/acts_as_textcaptcha/railtie.rb
|
204
|
+
- lib/acts_as_textcaptcha/tasks/textcaptcha.rake
|
183
205
|
- lib/acts_as_textcaptcha/textcaptcha.rb
|
184
206
|
- lib/acts_as_textcaptcha/textcaptcha_api.rb
|
185
207
|
- lib/acts_as_textcaptcha/textcaptcha_cache.rb
|
208
|
+
- lib/acts_as_textcaptcha/textcaptcha_config.rb
|
186
209
|
- lib/acts_as_textcaptcha/textcaptcha_helper.rb
|
187
210
|
- lib/acts_as_textcaptcha/version.rb
|
188
|
-
- lib/tasks/textcaptcha.rake
|
189
|
-
- test/schema.rb
|
190
|
-
- test/test_helper.rb
|
191
|
-
- test/test_models.rb
|
192
|
-
- test/textcaptcha_api_test.rb
|
193
|
-
- test/textcaptcha_cache_test.rb
|
194
|
-
- test/textcaptcha_helper_test.rb
|
195
|
-
- test/textcaptcha_test.rb
|
196
211
|
homepage: http://github.com/matthutchinson/acts_as_textcaptcha
|
197
212
|
licenses:
|
198
213
|
- MIT
|
199
214
|
metadata:
|
215
|
+
homepage_uri: https://github.com/matthutchinson/acts_as_textcaptcha
|
216
|
+
changelog_uri: https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CHANGELOG.md
|
217
|
+
source_code_uri: https://github.com/matthutchinson/acts_as_textcaptcha
|
218
|
+
bug_tracker_uri: https://github.com/matthutchinson/acts_as_textcaptcha/issues
|
200
219
|
allowed_push_host: https://rubygems.org
|
201
|
-
post_install_message:
|
202
|
-
rdoc_options:
|
220
|
+
post_install_message:
|
221
|
+
rdoc_options:
|
222
|
+
- "--title"
|
223
|
+
- ActAsTextcaptcha
|
224
|
+
- "--main"
|
225
|
+
- README.md
|
226
|
+
- "-ri"
|
203
227
|
require_paths:
|
204
228
|
- lib
|
205
229
|
required_ruby_version: !ruby/object:Gem::Requirement
|
206
230
|
requirements:
|
207
231
|
- - ">="
|
208
232
|
- !ruby/object:Gem::Version
|
209
|
-
version: '
|
233
|
+
version: '2.5'
|
210
234
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
211
235
|
requirements:
|
212
236
|
- - ">="
|
213
237
|
- !ruby/object:Gem::Version
|
214
238
|
version: '0'
|
215
239
|
requirements: []
|
216
|
-
|
217
|
-
|
218
|
-
signing_key:
|
240
|
+
rubygems_version: 3.2.3
|
241
|
+
signing_key:
|
219
242
|
specification_version: 4
|
220
|
-
summary:
|
221
|
-
API
|
243
|
+
summary: A text-based logic question captcha for Rails
|
222
244
|
test_files: []
|