acts_as_textcaptcha 4.5.1 → 4.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|