acts_as_textcaptcha 4.5.1 → 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/.rubocop.yml +1165 -0
- data/.simplecov +4 -2
- data/.travis.yml +14 -18
- data/Appraisals +3 -8
- data/CHANGELOG.md +9 -1
- data/Gemfile +3 -1
- data/README.md +18 -7
- data/Rakefile +18 -9
- data/acts_as_textcaptcha.gemspec +31 -26
- data/gemfiles/rails_4.gemfile +1 -1
- data/gemfiles/rails_5.gemfile +1 -1
- data/gemfiles/rails_6.gemfile +1 -1
- data/lib/acts_as_textcaptcha.rb +7 -7
- data/lib/acts_as_textcaptcha/railtie.rb +2 -2
- data/lib/acts_as_textcaptcha/tasks/textcaptcha.rake +6 -5
- data/lib/acts_as_textcaptcha/textcaptcha.rb +89 -95
- data/lib/acts_as_textcaptcha/textcaptcha_api.rb +34 -37
- data/lib/acts_as_textcaptcha/textcaptcha_cache.rb +6 -9
- data/lib/acts_as_textcaptcha/textcaptcha_config.rb +35 -36
- data/lib/acts_as_textcaptcha/textcaptcha_helper.rb +10 -13
- data/lib/acts_as_textcaptcha/version.rb +1 -1
- metadata +38 -23
data/.simplecov
CHANGED
data/.travis.yml
CHANGED
@@ -1,33 +1,31 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
|
-
script: 'bundle exec rake test'
|
4
3
|
gemfile:
|
5
|
-
- gemfiles/rails_3.gemfile
|
6
4
|
- gemfiles/rails_4.gemfile
|
7
5
|
- gemfiles/rails_5.gemfile
|
8
6
|
- gemfiles/rails_6.gemfile
|
9
7
|
|
10
8
|
rvm:
|
11
|
-
- 2.
|
12
|
-
- 2.
|
13
|
-
- 2.
|
14
|
-
-
|
9
|
+
- 2.5.8
|
10
|
+
- 2.6.6
|
11
|
+
- 2.7.2
|
12
|
+
- 3.0.0
|
15
13
|
- ruby-head
|
16
14
|
|
17
15
|
matrix:
|
18
16
|
allow_failures:
|
19
17
|
- rvm: ruby-head
|
20
18
|
exclude:
|
21
|
-
- rvm: 2.
|
22
|
-
gemfile: gemfiles/rails_6.gemfile
|
23
|
-
- rvm: 2.7.0
|
24
|
-
gemfile: gemfiles/rails_3.gemfile
|
25
|
-
- rvm: 2.7.0
|
19
|
+
- rvm: 2.7.2
|
26
20
|
gemfile: gemfiles/rails_4.gemfile
|
27
|
-
- rvm:
|
28
|
-
gemfile: gemfiles/
|
21
|
+
- rvm: 3.0.0
|
22
|
+
gemfile: gemfiles/rails_4.gemfile
|
23
|
+
- rvm: 3.0.0
|
24
|
+
gemfile: gemfiles/rails_5.gemfile
|
29
25
|
- rvm: ruby-head
|
30
26
|
gemfile: gemfiles/rails_4.gemfile
|
27
|
+
- rvm: ruby-head
|
28
|
+
gemfile: gemfiles/rails_5.gemfile
|
31
29
|
|
32
30
|
deploy:
|
33
31
|
provider: rubygems
|
@@ -40,12 +38,10 @@ deploy:
|
|
40
38
|
env:
|
41
39
|
global:
|
42
40
|
- CC_TEST_REPORTER_ID=3ff570478529bcdd11ef42d33229702118aa36b17a3de01c3f6d5a9c58fc7a4c
|
43
|
-
|
44
|
-
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
|
45
|
-
- gem install bundler -v '< 2'
|
41
|
+
- GIT_COMMITTED_AT=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then git log -1 --pretty=format:%ct; else git log -1 --skip 1 --pretty=format:%ct; fi)
|
46
42
|
before_script:
|
47
43
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
48
44
|
- chmod +x ./cc-test-reporter
|
49
|
-
- ./cc-test-reporter before-build
|
45
|
+
- ./cc-test-reporter before-build - GIT_COMMITTED_AT=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then git log -1 --pretty=format:%ct; else git log -1 --skip 1 --pretty=format:%ct; fi)
|
50
46
|
after_script:
|
51
|
-
- ./cc-test-reporter after-build
|
47
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT -t simplecov
|
data/Appraisals
CHANGED
@@ -1,19 +1,14 @@
|
|
1
|
-
appraise "rails-3" do
|
2
|
-
gem "rails", "3.2.22.5"
|
3
|
-
gem "sqlite3", "~> 1.3.5"
|
4
|
-
end
|
5
|
-
|
6
1
|
appraise "rails-4" do
|
7
|
-
gem "rails", "4.2.11.
|
2
|
+
gem "rails", "4.2.11.3"
|
8
3
|
gem "sqlite3", "~> 1.3.5"
|
9
4
|
end
|
10
5
|
|
11
6
|
appraise "rails-5" do
|
12
|
-
gem "rails", "5.2.4.
|
7
|
+
gem "rails", "5.2.4.4"
|
13
8
|
gem "sqlite3", "~> 1.4.2"
|
14
9
|
end
|
15
10
|
|
16
11
|
appraise "rails-6" do
|
17
|
-
gem "rails", "6.0.
|
12
|
+
gem "rails", "6.0.3.4"
|
18
13
|
gem "sqlite3", "~> 1.4.2"
|
19
14
|
end
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,13 @@ adheres to [Semantic Versioning][Semver].
|
|
9
9
|
|
10
10
|
- Your contribution here!
|
11
11
|
|
12
|
+
## [4.5.2] - 2021-01-28
|
13
|
+
### Changed
|
14
|
+
- CI covers Latest Rails version 6,5,4 and Ruby 3,2.7,2.6,2.5
|
15
|
+
### Removed
|
16
|
+
- Support for Ruby < 2.5 (EOL versions not supported)
|
17
|
+
- No longer testing against Rails 3 in CI
|
18
|
+
|
12
19
|
## [4.5.1] - 2020-01-28
|
13
20
|
### Fixed
|
14
21
|
- add Rails Railtie to fix rake task loading
|
@@ -88,7 +95,8 @@ adheres to [Semantic Versioning][Semver].
|
|
88
95
|
- README updated.
|
89
96
|
- Test coverage improved.
|
90
97
|
|
91
|
-
[Unreleased]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.5.
|
98
|
+
[Unreleased]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.5.2...HEAD
|
99
|
+
[4.5.2]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.5.1...v4.5.2
|
92
100
|
[4.5.1]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.5.0...v4.5.1
|
93
101
|
[4.5.0]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.4.1...v4.5.0
|
94
102
|
[4.4.1]: https://github.com/matthutchinson/acts_as_textcaptcha/compare/v4.3.0...v4.4.1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -9,18 +9,18 @@
|
|
9
9
|
ActsAsTextcaptcha provides spam protection for Rails models with text-based
|
10
10
|
logic question captchas. Questions are fetched from [Rob
|
11
11
|
Tuley's](https://twitter.com/robtuley)
|
12
|
-
[textcaptcha.com](
|
12
|
+
[textcaptcha.com](https://textcaptcha.com/). They can be solved easily by humans
|
13
13
|
but are tough for robots to crack.
|
14
14
|
|
15
15
|
The gem can also be configured with your own questions; as an alternative, or as
|
16
16
|
a fallback to handle any API issues. For reasons on why logic based captchas
|
17
|
-
are a good idea visit [textcaptcha.com](
|
17
|
+
are a good idea visit [textcaptcha.com](https://textcaptcha.com).
|
18
18
|
|
19
19
|
## Requirements
|
20
20
|
|
21
|
-
* [Ruby](http://ruby-lang.org/) >= 2.
|
22
|
-
* [Rails](http://github.com/rails/rails) >=
|
23
|
-
* [Rails.cache](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores)
|
21
|
+
* [Ruby](http://ruby-lang.org/) >= 2.5
|
22
|
+
* [Rails](http://github.com/rails/rails) >= 4
|
23
|
+
* A valid [Rails.cache](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores) (not `:null_store`)
|
24
24
|
|
25
25
|
## Demo
|
26
26
|
|
@@ -59,7 +59,7 @@ def new
|
|
59
59
|
end
|
60
60
|
```
|
61
61
|
|
62
|
-
|
62
|
+
Add the question and answer fields to your form using the
|
63
63
|
`textcaptcha_fields` helper. Arrange the HTML within this block as you like.
|
64
64
|
|
65
65
|
```ruby
|
@@ -75,6 +75,17 @@ If you'd prefer to construct your own form elements, take a look at the HTML
|
|
75
75
|
produced
|
76
76
|
[here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_helper.rb).
|
77
77
|
|
78
|
+
Finally set a valid [cache
|
79
|
+
store](https://guides.rubyonrails.org/caching_with_rails.html#cache-stores) (not `:null_store`) for your environments:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# e.g. in each config/environments/*.rb
|
83
|
+
config.cache_store = :memory_store
|
84
|
+
```
|
85
|
+
|
86
|
+
You can run `rails dev:cache` on a modern generated Rails app to enable
|
87
|
+
a memory store cache in the development environment.
|
88
|
+
|
78
89
|
## Configuration
|
79
90
|
|
80
91
|
The following options are available (only `api_key` is required):
|
@@ -241,7 +252,7 @@ The code is available as open source under the terms of
|
|
241
252
|
## Who's who?
|
242
253
|
|
243
254
|
* [ActsAsTextcaptcha](http://github.com/matthutchinson/acts_as_textcaptcha) and [little robot drawing](http://www.flickr.com/photos/hiddenloop/4541195635/) by [Matthew Hutchinson](http://matthewhutchinson.net)
|
244
|
-
* [TextCaptcha](
|
255
|
+
* [TextCaptcha](https://textcaptcha.com) API and service by [Rob Tuley](https://twitter.com/robtuley)
|
245
256
|
|
246
257
|
## Links
|
247
258
|
|
data/Rakefile
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
3
5
|
require "rdoc/task"
|
6
|
+
require "rubocop/rake_task"
|
4
7
|
|
5
8
|
# generate docs
|
6
9
|
RDoc::Task.new do |rd|
|
7
|
-
rd.main
|
8
|
-
rd.title
|
9
|
-
rd.rdoc_dir =
|
10
|
-
rd.options
|
10
|
+
rd.main = "README.md"
|
11
|
+
rd.title = "ActsAsTextcaptcha"
|
12
|
+
rd.rdoc_dir = "doc"
|
13
|
+
rd.options << "--all"
|
11
14
|
rd.rdoc_files.include("README.md", "LICENSE", "lib/**/*.rb")
|
12
15
|
end
|
13
16
|
|
@@ -18,13 +21,19 @@ Rake::TestTask.new(:test) do |t|
|
|
18
21
|
t.test_files = FileList["test/**/*_test.rb"]
|
19
22
|
end
|
20
23
|
|
24
|
+
# run lint
|
25
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
26
|
+
t.options = ["--display-cop-names"]
|
27
|
+
end
|
28
|
+
|
21
29
|
# run tests with code coverage (default)
|
22
30
|
namespace :test do
|
23
31
|
desc "Run all tests and features and generate a code coverage report"
|
24
32
|
task :coverage do
|
25
|
-
ENV[
|
26
|
-
Rake::Task[
|
33
|
+
ENV["COVERAGE"] = "true"
|
34
|
+
Rake::Task["test"].execute
|
35
|
+
Rake::Task["rubocop"].execute
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
30
|
-
task :
|
39
|
+
task default: [:rubocop, "test:coverage"]
|
data/acts_as_textcaptcha.gemspec
CHANGED
@@ -1,34 +1,36 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require "acts_as_textcaptcha/version"
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
8
|
+
spec.name = "acts_as_textcaptcha"
|
9
|
+
spec.version = ActsAsTextcaptcha::VERSION
|
10
|
+
spec.authors = ["Matthew Hutchinson"]
|
11
|
+
spec.email = ["matt@hiddenloop.com"]
|
10
12
|
spec.homepage = "http://github.com/matthutchinson/acts_as_textcaptcha"
|
11
|
-
spec.license
|
12
|
-
spec.summary
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.summary = "A text-based logic question captcha for Rails"
|
13
15
|
|
14
|
-
spec.description = <<-
|
16
|
+
spec.description = <<-DESCRIPTION
|
15
17
|
ActsAsTextcaptcha provides spam protection for Rails models with text-based
|
16
18
|
logic question captchas. Questions are fetched from Rob Tuley's
|
17
19
|
textcaptcha.com They can be solved easily by humans but are tough for robots
|
18
20
|
to crack.
|
19
|
-
|
21
|
+
DESCRIPTION
|
20
22
|
|
21
23
|
spec.metadata = {
|
22
|
-
"homepage_uri"
|
23
|
-
"changelog_uri"
|
24
|
-
"source_code_uri"
|
25
|
-
"bug_tracker_uri"
|
24
|
+
"homepage_uri" => "https://github.com/matthutchinson/acts_as_textcaptcha",
|
25
|
+
"changelog_uri" => "https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CHANGELOG.md",
|
26
|
+
"source_code_uri" => "https://github.com/matthutchinson/acts_as_textcaptcha",
|
27
|
+
"bug_tracker_uri" => "https://github.com/matthutchinson/acts_as_textcaptcha/issues",
|
26
28
|
"allowed_push_host" => "https://rubygems.org"
|
27
29
|
}
|
28
30
|
|
29
|
-
spec.files
|
30
|
-
spec.test_files
|
31
|
-
spec.bindir
|
31
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
32
|
+
spec.test_files = `git ls-files -- {test}/*`.split("\n")
|
33
|
+
spec.bindir = "bin"
|
32
34
|
spec.require_paths = ["lib"]
|
33
35
|
|
34
36
|
# documentation
|
@@ -36,21 +38,24 @@ Gem::Specification.new do |spec|
|
|
36
38
|
spec.rdoc_options << "--title" << "ActAsTextcaptcha" << "--main" << "README.md" << "-ri"
|
37
39
|
|
38
40
|
# non-gem dependecies
|
39
|
-
spec.required_ruby_version = ">= 2.
|
41
|
+
spec.required_ruby_version = ">= 2.5"
|
40
42
|
|
41
43
|
# dev gems
|
42
|
-
spec.add_development_dependency(
|
44
|
+
spec.add_development_dependency("bundler")
|
45
|
+
spec.add_development_dependency("pry-byebug")
|
43
46
|
spec.add_development_dependency "rake"
|
44
|
-
|
47
|
+
|
48
|
+
# Lint
|
49
|
+
spec.add_development_dependency("rubocop")
|
45
50
|
|
46
51
|
# docs
|
47
|
-
spec.add_development_dependency(
|
52
|
+
spec.add_development_dependency("rdoc")
|
48
53
|
|
49
54
|
# testing
|
50
|
-
spec.add_development_dependency(
|
51
|
-
spec.add_development_dependency(
|
52
|
-
spec.add_development_dependency(
|
53
|
-
spec.add_development_dependency(
|
54
|
-
spec.add_development_dependency(
|
55
|
-
spec.add_development_dependency(
|
55
|
+
spec.add_development_dependency("appraisal")
|
56
|
+
spec.add_development_dependency("minitest")
|
57
|
+
spec.add_development_dependency("rails", "~> 6.0.3.4")
|
58
|
+
spec.add_development_dependency("simplecov", "~> 0.19.1")
|
59
|
+
spec.add_development_dependency("sqlite3")
|
60
|
+
spec.add_development_dependency("webmock")
|
56
61
|
end
|
data/gemfiles/rails_4.gemfile
CHANGED
data/gemfiles/rails_5.gemfile
CHANGED
data/gemfiles/rails_6.gemfile
CHANGED
data/lib/acts_as_textcaptcha.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
3
|
+
require "acts_as_textcaptcha/version"
|
4
|
+
require "acts_as_textcaptcha/errors"
|
5
|
+
require "acts_as_textcaptcha/textcaptcha"
|
6
|
+
require "acts_as_textcaptcha/textcaptcha_config"
|
7
|
+
require "acts_as_textcaptcha/textcaptcha_helper"
|
8
|
+
require "acts_as_textcaptcha/framework/rails"
|
9
|
+
require "acts_as_textcaptcha/railtie" if defined?(Rails::Railtie)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
3
|
module ActsAsTextcaptcha
|
4
4
|
class Railtie < Rails::Railtie
|
5
5
|
rake_tasks do
|
6
|
-
Dir[File.join(File.dirname(__FILE__),
|
6
|
+
Dir[File.join(File.dirname(__FILE__), "/tasks/*.rake")].each { |f| load f }
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -1,16 +1,17 @@
|
|
1
|
-
|
2
|
-
require 'acts_as_textcaptcha/textcaptcha_config'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require "rails"
|
4
|
+
require "acts_as_textcaptcha/textcaptcha_config"
|
5
5
|
|
6
|
+
namespace :textcaptcha do
|
6
7
|
desc "Creates an example textcaptcha config at config/textcaptcha.yml"
|
7
8
|
task :config do
|
8
|
-
path = File.join((Rails.root
|
9
|
+
path = File.join((Rails.root || "."), "config", "textcaptcha.yml")
|
9
10
|
if File.exist?(path)
|
10
11
|
puts "Ooops, a textcaptcha config file at #{path} already exists ... aborting."
|
11
12
|
else
|
12
13
|
ActsAsTextcaptcha::TextcaptchaConfig.create(path: path)
|
13
|
-
puts "Done, config generated at #{path}\nEdit this file to add your TextCaptcha API key (see
|
14
|
+
puts "Done, config generated at #{path}\nEdit this file to add your TextCaptcha API key (see https://textcaptcha.com)."
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
@@ -1,22 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "yaml"
|
4
|
+
require "net/http"
|
5
|
+
require "digest/md5"
|
6
|
+
require "acts_as_textcaptcha/textcaptcha_cache"
|
7
|
+
require "acts_as_textcaptcha/textcaptcha_api"
|
8
8
|
|
9
9
|
module ActsAsTextcaptcha
|
10
10
|
module Textcaptcha
|
11
|
-
|
12
11
|
def acts_as_textcaptcha(options = nil)
|
13
12
|
cattr_accessor :textcaptcha_config
|
14
13
|
attr_accessor :textcaptcha_question, :textcaptcha_answer, :textcaptcha_key
|
15
14
|
|
16
15
|
# ensure these attrs are accessible (Rails 3)
|
17
|
-
if respond_to?(:accessible_attributes) && respond_to?(:attr_accessible)
|
18
|
-
attr_accessible :textcaptcha_answer, :textcaptcha_key
|
19
|
-
end
|
16
|
+
attr_accessible :textcaptcha_answer, :textcaptcha_key if respond_to?(:accessible_attributes) && respond_to?(:attr_accessible)
|
20
17
|
|
21
18
|
self.textcaptcha_config = build_textcaptcha_config(options).symbolize_keys!
|
22
19
|
|
@@ -26,122 +23,119 @@ module ActsAsTextcaptcha
|
|
26
23
|
end
|
27
24
|
|
28
25
|
module InstanceMethods
|
29
|
-
|
30
26
|
# override this method to toggle textcaptcha checking, by default this
|
31
27
|
# will only allow new records to be protected with textcaptchas
|
32
28
|
def perform_textcaptcha?
|
33
|
-
(!respond_to?(
|
29
|
+
(!respond_to?("new_record?") || new_record?)
|
34
30
|
end
|
35
31
|
|
36
32
|
def textcaptcha
|
37
|
-
if perform_textcaptcha? && textcaptcha_config
|
38
|
-
assign_textcaptcha(fetch_q_and_a || config_q_and_a)
|
39
|
-
end
|
33
|
+
assign_textcaptcha(fetch_q_and_a || config_q_and_a) if perform_textcaptcha? && textcaptcha_config
|
40
34
|
end
|
41
35
|
|
42
|
-
|
43
36
|
private
|
44
37
|
|
45
|
-
|
46
|
-
|
38
|
+
def fetch_q_and_a
|
39
|
+
return unless should_fetch?
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
TextcaptchaApi.new(
|
42
|
+
api_key: textcaptcha_config[:api_key],
|
43
|
+
api_endpoint: textcaptcha_config[:api_endpoint],
|
44
|
+
raise_errors: textcaptcha_config[:raise_errors]
|
45
|
+
).fetch
|
46
|
+
end
|
54
47
|
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
def should_fetch?
|
49
|
+
textcaptcha_config[:api_key] || textcaptcha_config[:api_endpoint]
|
50
|
+
end
|
58
51
|
|
59
|
-
|
60
|
-
|
61
|
-
random_question = textcaptcha_config[:questions][rand(textcaptcha_config[:questions].size)].symbolize_keys!
|
62
|
-
answers = (random_question[:answers] || '').split(',').map!{ |answer| safe_md5(answer) }
|
63
|
-
if random_question && answers.present?
|
64
|
-
{ 'q' => random_question[:question], 'a' => answers }
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
52
|
+
def config_q_and_a
|
53
|
+
return unless textcaptcha_config[:questions]
|
68
54
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if valid_answers.include?(safe_md5(textcaptcha_answer))
|
74
|
-
# answer was valid, mutate the key again
|
75
|
-
self.textcaptcha_key = textcaptcha_random_key
|
76
|
-
textcaptcha_cache.write(textcaptcha_key, valid_answers, textcaptcha_cache_options)
|
77
|
-
true
|
78
|
-
else
|
79
|
-
add_textcaptcha_error(too_slow: valid_answers.empty?)
|
80
|
-
textcaptcha
|
81
|
-
false
|
82
|
-
end
|
83
|
-
end
|
55
|
+
random_question = textcaptcha_config[:questions][rand(textcaptcha_config[:questions].size)].symbolize_keys!
|
56
|
+
answers = (random_question[:answers] || "").split(",").map { |answer| safe_md5(answer) }
|
57
|
+
{ "q" => random_question[:question], "a" => answers } if random_question && answers.present?
|
58
|
+
end
|
84
59
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
60
|
+
# check textcaptcha, if incorrect, generate a new textcaptcha
|
61
|
+
def validate_textcaptcha
|
62
|
+
valid_answers = textcaptcha_cache.read(textcaptcha_key) || []
|
63
|
+
reset_textcaptcha
|
64
|
+
if valid_answers.include?(safe_md5(textcaptcha_answer))
|
65
|
+
# answer was valid, mutate the key again
|
66
|
+
self.textcaptcha_key = textcaptcha_random_key
|
67
|
+
textcaptcha_cache.write(textcaptcha_key, valid_answers, textcaptcha_cache_options)
|
68
|
+
true
|
69
|
+
else
|
70
|
+
add_textcaptcha_error(too_slow: valid_answers.empty?)
|
71
|
+
textcaptcha
|
72
|
+
false
|
91
73
|
end
|
74
|
+
end
|
92
75
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
76
|
+
def add_textcaptcha_error(too_slow: false)
|
77
|
+
if too_slow
|
78
|
+
errors.add(:textcaptcha_answer, :expired, message: "was not submitted quickly enough, try another question instead")
|
79
|
+
else
|
80
|
+
errors.add(:textcaptcha_answer, :incorrect, message: "is incorrect, try another question instead")
|
98
81
|
end
|
82
|
+
end
|
99
83
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
self.textcaptcha_key =
|
104
|
-
textcaptcha_cache.write(textcaptcha_key, q_and_a['a'], textcaptcha_cache_options)
|
84
|
+
def reset_textcaptcha
|
85
|
+
if textcaptcha_key
|
86
|
+
textcaptcha_cache.delete(textcaptcha_key)
|
87
|
+
self.textcaptcha_key = nil
|
105
88
|
end
|
89
|
+
end
|
106
90
|
|
107
|
-
|
108
|
-
|
109
|
-
def safe_md5(answer)
|
110
|
-
Digest::MD5.hexdigest(answer.to_s.strip.mb_chars.downcase)
|
111
|
-
end
|
91
|
+
def assign_textcaptcha(q_and_a)
|
92
|
+
return unless q_and_a
|
112
93
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
94
|
+
self.textcaptcha_question = q_and_a["q"]
|
95
|
+
self.textcaptcha_key = textcaptcha_random_key
|
96
|
+
textcaptcha_cache.write(textcaptcha_key, q_and_a["a"], textcaptcha_cache_options)
|
97
|
+
end
|
117
98
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
99
|
+
# strip whitespace pass through mb_chars (a multibyte safe proxy for
|
100
|
+
# strings) then downcase
|
101
|
+
def safe_md5(answer)
|
102
|
+
Digest::MD5.hexdigest(answer.to_s.strip.mb_chars.downcase)
|
103
|
+
end
|
104
|
+
|
105
|
+
# a random cache key, time based, random
|
106
|
+
def textcaptcha_random_key
|
107
|
+
safe_md5(Time.now.to_i + rand(1_000_000))
|
108
|
+
end
|
125
109
|
|
126
|
-
|
127
|
-
|
110
|
+
def textcaptcha_cache_options
|
111
|
+
if textcaptcha_config[:cache_expiry_minutes]
|
112
|
+
{ expires_in: textcaptcha_config[:cache_expiry_minutes].to_f.minutes }
|
113
|
+
else
|
114
|
+
{}
|
128
115
|
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def textcaptcha_cache
|
119
|
+
@textcaptcha_cache ||= TextcaptchaCache.new
|
120
|
+
end
|
129
121
|
end
|
130
122
|
|
131
123
|
private
|
132
124
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
rescue
|
140
|
-
raise ArgumentError.new('could not find any textcaptcha options, in config/textcaptcha.yml or model - run rake textcaptcha:config to generate a template config file')
|
125
|
+
# rubocop:disable Security/YAMLLoad
|
126
|
+
def build_textcaptcha_config(options)
|
127
|
+
if options.is_a?(Hash)
|
128
|
+
options
|
129
|
+
else
|
130
|
+
YAML.load(ERB.new(read_textcaptcha_config).result)[Rails.env]
|
141
131
|
end
|
132
|
+
rescue StandardError
|
133
|
+
raise ArgumentError, "could not find any textcaptcha options, in config/textcaptcha.yml or model - run rake textcaptcha:config to generate a template config file"
|
134
|
+
end
|
135
|
+
# rubocop:enable Security/YAMLLoad
|
142
136
|
|
143
|
-
|
144
|
-
|
145
|
-
|
137
|
+
def read_textcaptcha_config
|
138
|
+
File.read("#{Rails.root || "."}/config/textcaptcha.yml")
|
139
|
+
end
|
146
140
|
end
|
147
141
|
end
|