acts_as_textcaptcha 4.2.0 → 4.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rubocop.yml +1165 -0
  4. data/.simplecov +11 -0
  5. data/.travis.yml +30 -8
  6. data/Appraisals +9 -6
  7. data/CHANGELOG.md +94 -34
  8. data/CODE_OF_CONDUCT.md +54 -31
  9. data/CONTRIBUTING.md +14 -9
  10. data/Gemfile +2 -2
  11. data/LICENSE +165 -0
  12. data/PULL_REQUEST_TEMPLATE.md +16 -0
  13. data/README.md +206 -228
  14. data/Rakefile +26 -19
  15. data/acts_as_textcaptcha.gemspec +57 -41
  16. data/bin/console +2 -5
  17. data/bin/setup +7 -0
  18. data/gemfiles/rails_3.gemfile +1 -0
  19. data/gemfiles/rails_4.gemfile +2 -1
  20. data/gemfiles/rails_5.gemfile +2 -1
  21. data/gemfiles/rails_6.gemfile +8 -0
  22. data/lib/acts_as_textcaptcha.rb +9 -4
  23. data/lib/acts_as_textcaptcha/errors.rb +21 -0
  24. data/lib/acts_as_textcaptcha/framework/rails.rb +3 -1
  25. data/lib/acts_as_textcaptcha/railtie.rb +9 -0
  26. data/lib/acts_as_textcaptcha/tasks/textcaptcha.rake +17 -0
  27. data/lib/acts_as_textcaptcha/textcaptcha.rb +75 -72
  28. data/lib/acts_as_textcaptcha/textcaptcha_api.rb +39 -45
  29. data/lib/acts_as_textcaptcha/textcaptcha_cache.rb +12 -16
  30. data/lib/acts_as_textcaptcha/textcaptcha_config.rb +47 -0
  31. data/lib/acts_as_textcaptcha/textcaptcha_helper.rb +13 -14
  32. data/lib/acts_as_textcaptcha/version.rb +3 -1
  33. metadata +68 -46
  34. data/.coveralls.yml +0 -1
  35. data/LICENSE.txt +0 -21
  36. data/config/textcaptcha.yml +0 -34
  37. data/lib/tasks/textcaptcha.rake +0 -21
  38. data/test/schema.rb +0 -34
  39. data/test/test_helper.rb +0 -44
  40. data/test/test_models.rb +0 -69
  41. data/test/textcaptcha_api_test.rb +0 -46
  42. data/test/textcaptcha_cache_test.rb +0 -25
  43. data/test/textcaptcha_helper_test.rb +0 -68
  44. data/test/textcaptcha_test.rb +0 -198
@@ -1,61 +1,55 @@
1
- # simple wrapper for the textcaptcha.com API service
2
- # loads and parses captcha question and answers
1
+ # frozen_string_literal: true
3
2
 
4
- require 'rexml/document'
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
- ENDPOINT = 'http://textcaptcha.com/api/'
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
- def self.parse(xml)
42
- parsed_xml = ActiveSupport::XmlMini.parse(xml)['captcha']
43
- question = parsed_xml['question']['__content__']
44
- if parsed_xml['answer'].is_a?(Array)
45
- answers = parsed_xml['answer'].collect { |a| a['__content__'] }
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
- answers = [parsed_xml['answer']['__content__']]
33
+ handle_error ResponseError.new(uri, "status: #{response.code}")
48
34
  end
49
-
50
- [question, answers]
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
- private
48
+ def handle_error(error)
49
+ raise error if raise_errors
55
50
 
56
- def self.uri_parser
57
- # URI.parse is deprecated in 1.9.2
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
- # A simple cache for storing Textcaptcha answers
2
- # by default the underlying cache implementation is
3
- # the standard Rails.cache (ActiveSupport::Cache)
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
- CACHE_KEY_PREFIX = 'acts_as_textcaptcha-'
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, options = nil)
19
- Rails.cache.read(cache_key(key), options)
16
+ def read(key)
17
+ Rails.cache.read(cache_key(key))
20
18
  end
21
19
 
22
- def delete(key, options = nil)
23
- Rails.cache.delete(cache_key(key), options)
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
- "#{CACHE_KEY_PREFIX}#{key}"
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
- # builds html form fields for the textcaptcha
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsTextcaptcha
2
- VERSION = "4.2.0"
4
+ VERSION = "4.5.2"
3
5
  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.0
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: 2018-01-17 00:00:00.000000000 Z
11
+ date: 2021-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.1.4
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: 5.1.4
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: mime-types
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: bundler
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: minitest
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: simplecov
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: rdoc
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: sqlite3
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: webmock
112
+ name: rails
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
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: '0'
124
+ version: 6.0.3.4
125
125
  - !ruby/object:Gem::Dependency
126
- name: coveralls
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: appraisal
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
- Simple question/answer based spam protection for your Rails models.
155
- You can define your own logic questions and/or fetch questions from the textcaptcha.com API.
156
- The questions involve human logic and are tough for spam bots to crack.
157
- For more reasons on why logic questions are a good idea visit; http://textcaptcha.com/why
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.txt
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
- - config/textcaptcha.yml
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: '0'
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
- rubyforge_project:
217
- rubygems_version: 2.7.3
218
- signing_key:
240
+ rubygems_version: 3.2.3
241
+ signing_key:
219
242
  specification_version: 4
220
- summary: Spam protection for your models via logic questions and the textcaptcha.com
221
- API
243
+ summary: A text-based logic question captcha for Rails
222
244
  test_files: []