acts_as_textcaptcha 1.2.1 → 2.1.1
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.
- data/README.rdoc +28 -12
- data/lib/acts_as_textcaptcha/framework/rails2.rb +2 -0
- data/lib/acts_as_textcaptcha/framework/rails3.rb +7 -0
- data/lib/acts_as_textcaptcha/textcaptcha.rb +119 -0
- data/lib/acts_as_textcaptcha/textcaptcha_helper.rb +9 -0
- data/lib/acts_as_textcaptcha.rb +3 -100
- data/spec/acts_as_textcaptcha_spec.rb +41 -20
- data/spec/schema.rb +4 -4
- data/spec/spec_helper.rb +2 -11
- metadata +9 -15
- data/.gitignore +0 -5
- data/Rakefile +0 -81
- data/VERSION +0 -1
- data/acts_as_textcaptcha.gemspec +0 -63
- data/config/textcaptcha.yml +0 -36
- data/init.rb +0 -1
- data/lib/textcaptcha_helper.rb +0 -7
- data/rails/init.rb +0 -9
- data/spec/database.yml +0 -21
- data/spec/spec.opts +0 -2
data/README.rdoc
CHANGED
@@ -8,6 +8,10 @@ The gem can be configured with your very own logic questions (to fall back on if
|
|
8
8
|
|
9
9
|
Text CAPTCHA's logic questions are aimed at a child's age of 7, so can be solved easily by all but the most cognitively impaired users. As they involve human logic, such questions cannot be solved by a robot. There are both advantages and disadvantages for using logic questions over image based captchas, {find out more at Text CAPTCHA}[http://textcaptcha.com/why].
|
10
10
|
|
11
|
+
== Rails 3
|
12
|
+
|
13
|
+
This plugin/gem is now fully Rails 3 compatible.
|
14
|
+
|
11
15
|
== Demo
|
12
16
|
|
13
17
|
Here's a {fully working demo on heroku}[http://textcaptcha.heroku.com]!
|
@@ -16,7 +20,7 @@ Here's a {fully working demo on heroku}[http://textcaptcha.heroku.com]!
|
|
16
20
|
|
17
21
|
What do you need?
|
18
22
|
|
19
|
-
* {Rails}[http://github.com/rails/rails] >= 2.3
|
23
|
+
* {Rails}[http://github.com/rails/rails] >= 2.3.x (including Rails 3)
|
20
24
|
* {Ruby}[http://ruby-lang.org/] >= 1.8.7
|
21
25
|
* {bcrypt-ruby}[http://bcrypt-ruby.rubyforge.org/] gem (to securely encrypt the spam answers in your session)
|
22
26
|
* {Text CAPTCHA api key}[http://textcaptcha.com/register] (_optional_, since you can define your own logic questions, see below for details)
|
@@ -24,19 +28,25 @@ What do you need?
|
|
24
28
|
|
25
29
|
== Installing
|
26
30
|
|
27
|
-
Install the
|
31
|
+
Install the gem
|
28
32
|
|
29
|
-
sudo gem install acts_as_textcaptcha
|
33
|
+
sudo gem install acts_as_textcaptcha
|
30
34
|
|
31
|
-
Or you can install acts_as_textcaptcha as a Rails plugin
|
35
|
+
Or you can install acts_as_textcaptcha as a Rails plugin
|
32
36
|
|
33
37
|
script/plugin install git://github.com/matthutchinson/acts_as_textcaptcha
|
38
|
+
(if you do this be sure to include {bcrypt-ruby}[http://bcrypt-ruby.rubyforge.org/] in your Gemfile or with config.gem in your environment)
|
34
39
|
|
35
|
-
== Using
|
40
|
+
== Using
|
41
|
+
|
42
|
+
First the gem to your Gemfile
|
43
|
+
|
44
|
+
gem "acts_as_textcaptcha"
|
36
45
|
|
37
|
-
|
46
|
+
OR add the gem in your environment.rb config;
|
38
47
|
|
39
48
|
config.gem 'acts_as_textcaptcha'
|
49
|
+
|
40
50
|
|
41
51
|
Next configure your models to be spam protected; (this is the most basic way to configure the gem, with an api key only)
|
42
52
|
|
@@ -63,11 +73,13 @@ Next in your controller *new* and *create* actions you'll want to _spamify_ your
|
|
63
73
|
|
64
74
|
Finally, in your form/view erb do something like the following;
|
65
75
|
|
66
|
-
<%- if @comment.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
76
|
+
<%- if @comment.new_record? -%>
|
77
|
+
<%- if @comment.validate_spam_answer -%>
|
78
|
+
<%= f.hidden_field :spam_answer, :value => @comment.spam_answer -%>
|
79
|
+
<%- else -%>
|
80
|
+
<%= f.label :spam_answer, @comment.spam_question %>
|
81
|
+
<%= f.text_field :spam_answer, :value => '' %>
|
82
|
+
<%- end -%>
|
71
83
|
<%- end -%>
|
72
84
|
|
73
85
|
== More Configurations
|
@@ -101,7 +113,7 @@ It *also* is possible to configure to use only your own user defined logic quest
|
|
101
113
|
|
102
114
|
The gem contains two parts, a module for your ActiveRecord models, and a tiny helper method (spamify).
|
103
115
|
|
104
|
-
A call to spamify(@model) in your controller will query the Text CAPTCHA web service. A
|
116
|
+
A call to spamify(@model) in your controller will query the Text CAPTCHA web service. A GET request is made with Net::HTTP and parsed using the default Rails ActiveSupport::XMLMini backend (or the standard XML::Parser in older versions of Rails). A spam_question is assigned to the model, and an array of possible answers are encrypted in the session.
|
105
117
|
|
106
118
|
validate_spam_answer() is called on @model.validate() and checks that the @model.spam_answer matches one of those possible answers in the session. This validation is _only_ carried out on new records, i.e. never on edit, only on create. User's attempted spam answers are not case-sensitive and have trailing/leading white-space removed.
|
107
119
|
|
@@ -137,6 +149,10 @@ For more details on the code please check the {documentation}[http://rdoc.info/p
|
|
137
149
|
* {Text CAPTCHA}[http://textcaptcha.com] api and service by {Rob Tuley}[http://openknot.com/me/] at {Openknot}[http://openknot.com]
|
138
150
|
* {bcrypt-ruby}[http://bcrypt-ruby.rubyforge.org/] Gem by {Coda Hale}[http://codahale.com]
|
139
151
|
|
152
|
+
== Todo
|
153
|
+
|
154
|
+
* Test in other Ruby versions
|
155
|
+
|
140
156
|
== Usage
|
141
157
|
|
142
158
|
This code is currently used in a number of production websites and apps. It was originally extracted from code developed for {Bugle}[http://bugleblogs.com]
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'net/http'
|
3
|
+
require 'md5'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
# compatiblity with < Rails 3.0.0
|
7
|
+
require 'xml' unless defined?(ActiveSupport::XmlMini)
|
8
|
+
|
9
|
+
# if using as a plugin in /vendor/plugins
|
10
|
+
begin
|
11
|
+
require 'bcrypt'
|
12
|
+
rescue LoadError => e
|
13
|
+
puts "ActsAsTextcaptcha - please gem install bcrypt-ruby and add `gem \"bcrypt-ruby\"` to your Gemfile (or environment config)"
|
14
|
+
raise e
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActsAsTextcaptcha
|
18
|
+
module Textcaptcha #:nodoc:
|
19
|
+
|
20
|
+
def acts_as_textcaptcha(options = nil)
|
21
|
+
cattr_accessor :textcaptcha_config
|
22
|
+
attr_accessor :spam_answer, :spam_question, :possible_answers
|
23
|
+
validate :validate_textcaptcha
|
24
|
+
|
25
|
+
if options.is_a?(Hash)
|
26
|
+
self.textcaptcha_config = options
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
self.textcaptcha_config = YAML.load(File.read("#{Rails.root.to_s}/config/textcaptcha.yml"))[Rails.env]
|
30
|
+
rescue Errno::ENOENT
|
31
|
+
raise('./config/textcaptcha.yml not found')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
include InstanceMethods
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
|
41
|
+
# override this method to toggle spam checking, default is on (true)
|
42
|
+
def perform_spam_check?; true end
|
43
|
+
|
44
|
+
# override this method to toggle allowing the model to be created, default is on (true)
|
45
|
+
# if returning false model.validate will always be false with errors on base
|
46
|
+
def allowed?; true end
|
47
|
+
|
48
|
+
def validate_textcaptcha
|
49
|
+
if new_record?
|
50
|
+
if allowed?
|
51
|
+
if possible_answers && perform_spam_check? && !validate_spam_answer
|
52
|
+
errors.add(:spam_answer, 'is incorrect, try another question instead')
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
else
|
56
|
+
errors.add(:base, "Sorry, #{self.class.name.pluralize.downcase} are currently disabled")
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_spam_answer
|
64
|
+
(spam_answer && possible_answers) ? possible_answers.include?(encrypt_answer(Digest::MD5.hexdigest(spam_answer.strip.downcase.to_s))) : false
|
65
|
+
end
|
66
|
+
|
67
|
+
def encrypt_answers(answers)
|
68
|
+
answers.map {|answer| encrypt_answer(answer) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def encrypt_answer(answer)
|
72
|
+
return answer unless(textcaptcha_config['bcrypt_salt'])
|
73
|
+
BCrypt::Engine.hash_secret(answer, textcaptcha_config['bcrypt_salt'], (textcaptcha_config['bcrypt_cost'] || 10))
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_spam_question(use_textcaptcha = true)
|
77
|
+
if use_textcaptcha && textcaptcha_config && textcaptcha_config['api_key']
|
78
|
+
begin
|
79
|
+
resp = Net::HTTP.get(URI.parse('http://textcaptcha.com/api/'+textcaptcha_config['api_key']))
|
80
|
+
return [] if resp.empty?
|
81
|
+
|
82
|
+
if defined?(ActiveSupport::XmlMini)
|
83
|
+
parsed_xml = ActiveSupport::XmlMini.parse(resp)['captcha']
|
84
|
+
self.spam_question = parsed_xml['question']['__content__']
|
85
|
+
if parsed_xml['answer'].is_a?(Array)
|
86
|
+
self.possible_answers = encrypt_answers(parsed_xml['answer'].collect {|a| a['__content__']})
|
87
|
+
else
|
88
|
+
self.possible_answers = encrypt_answers([parsed_xml['answer']['__content__']])
|
89
|
+
end
|
90
|
+
else
|
91
|
+
parsed_xml = XML::Parser.string(resp).parse
|
92
|
+
self.spam_question = parsed_xml.find('/captcha/question')[0].inner_xml
|
93
|
+
self.possible_answers = encrypt_answers(parsed_xml.find('/captcha/answer').map(&:inner_xml))
|
94
|
+
end
|
95
|
+
return possible_answers if spam_question && !possible_answers.empty?
|
96
|
+
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNREFUSED,
|
97
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, URI::InvalidURIError => e
|
98
|
+
log_textcaptcha("failed to load or parse textcaptcha with key '#{textcaptcha_config['api_key']}'; #{e}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# fall back to textcaptcha_config questions
|
103
|
+
if textcaptcha_config && textcaptcha_config['questions']
|
104
|
+
log_textcaptcha('falling back to random logic question from config') if textcaptcha_config['api_key']
|
105
|
+
random_question = textcaptcha_config['questions'][rand(textcaptcha_config['questions'].size)]
|
106
|
+
self.spam_question = random_question['question']
|
107
|
+
self.possible_answers = encrypt_answers(random_question['answers'].split(',').map!{|ans| Digest::MD5.hexdigest(ans)})
|
108
|
+
end
|
109
|
+
possible_answers
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def log_textcaptcha(message)
|
114
|
+
logger ||= Logger.new(STDOUT)
|
115
|
+
logger.info "Textcaptcha >> #{message}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module ActsAsTextcaptcha
|
2
|
+
module TextcaptchaHelper
|
3
|
+
|
4
|
+
# generates a spam question and possible answers for a model, answers are stored in session[:possible_answers]
|
5
|
+
def spamify(model)
|
6
|
+
session[:possible_answers] = model.generate_spam_question unless model.validate_spam_answer
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/acts_as_textcaptcha.rb
CHANGED
@@ -1,100 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'net/http'
|
5
|
-
require 'md5'
|
6
|
-
require 'logger'
|
7
|
-
require 'bcrypt'
|
8
|
-
rescue LoadError
|
9
|
-
end
|
10
|
-
|
11
|
-
module ActsAsTextcaptcha #:nodoc:
|
12
|
-
|
13
|
-
def acts_as_textcaptcha(options = nil)
|
14
|
-
cattr_accessor :textcaptcha_config
|
15
|
-
attr_accessor :spam_answer, :spam_question, :possible_answers
|
16
|
-
|
17
|
-
if options.is_a?(Hash)
|
18
|
-
self.textcaptcha_config = options
|
19
|
-
else
|
20
|
-
begin
|
21
|
-
self.textcaptcha_config = YAML.load(File.read("#{(defined? RAILS_ROOT) ? "#{RAILS_ROOT}" : '.'}/config/textcaptcha.yml"))[((defined? RAILS_ENV) ? RAILS_ENV : 'test')]
|
22
|
-
rescue Errno::ENOENT
|
23
|
-
raise('./config/textcaptcha.yml not found')
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
include InstanceMethods
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
module InstanceMethods
|
32
|
-
|
33
|
-
# override this method to toggle spam checking, default is on (true)
|
34
|
-
def perform_spam_check?; true end
|
35
|
-
|
36
|
-
# override this method to toggle allowing the model to be created, default is on (true)
|
37
|
-
# if returning false model.validate will always be false with errors on base
|
38
|
-
def allowed?; true end
|
39
|
-
|
40
|
-
def validate
|
41
|
-
super
|
42
|
-
if new_record?
|
43
|
-
if allowed?
|
44
|
-
if possible_answers && perform_spam_check? && !validate_spam_answer
|
45
|
-
errors.add(:spam_answer, 'is incorrect, try another question instead')
|
46
|
-
return false
|
47
|
-
end
|
48
|
-
else
|
49
|
-
errors.add_to_base("Sorry, #{self.class.name.pluralize.downcase} are currently disabled")
|
50
|
-
return false
|
51
|
-
end
|
52
|
-
end
|
53
|
-
true
|
54
|
-
end
|
55
|
-
|
56
|
-
def validate_spam_answer
|
57
|
-
(spam_answer && possible_answers) ? possible_answers.include?(encrypt_answer(Digest::MD5.hexdigest(spam_answer.strip.downcase.to_s))) : false
|
58
|
-
end
|
59
|
-
|
60
|
-
def encrypt_answers(answers)
|
61
|
-
answers.map {|answer| encrypt_answer(answer) }
|
62
|
-
end
|
63
|
-
|
64
|
-
def encrypt_answer(answer)
|
65
|
-
return answer unless(textcaptcha_config['bcrypt_salt'])
|
66
|
-
BCrypt::Engine.hash_secret(answer, textcaptcha_config['bcrypt_salt'], (textcaptcha_config['bcrypt_cost'] || 10))
|
67
|
-
end
|
68
|
-
|
69
|
-
def generate_spam_question(use_textcaptcha = true)
|
70
|
-
if use_textcaptcha && textcaptcha_config && textcaptcha_config['api_key']
|
71
|
-
begin
|
72
|
-
resp = Net::HTTP.get(URI.parse('http://textcaptcha.com/api/'+textcaptcha_config['api_key']))
|
73
|
-
if !resp.empty? && xml = XML::Parser.string(resp).parse
|
74
|
-
self.spam_question = xml.find('/captcha/question')[0].inner_xml
|
75
|
-
self.possible_answers = encrypt_answers(xml.find('/captcha/answer').map(&:inner_xml))
|
76
|
-
end
|
77
|
-
return possible_answers if spam_question && !possible_answers.empty?
|
78
|
-
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNREFUSED,
|
79
|
-
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, URI::InvalidURIError => e
|
80
|
-
log_textcaptcha("failed to load or parse textcaptcha with key '#{textcaptcha_config['api_key']}'; #{e}")
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# fall back to textcaptcha_config questions
|
85
|
-
if textcaptcha_config && textcaptcha_config['questions']
|
86
|
-
log_textcaptcha('falling back to random logic question from config') if textcaptcha_config['api_key']
|
87
|
-
random_question = textcaptcha_config['questions'][rand(textcaptcha_config['questions'].size)]
|
88
|
-
self.spam_question = random_question['question']
|
89
|
-
self.possible_answers = encrypt_answers(random_question['answers'].split(',').map!{|ans| Digest::MD5.hexdigest(ans)})
|
90
|
-
end
|
91
|
-
possible_answers
|
92
|
-
end
|
93
|
-
|
94
|
-
private
|
95
|
-
def log_textcaptcha(message)
|
96
|
-
logger ||= Logger.new(STDOUT)
|
97
|
-
logger.info "Textcaptcha >> #{message}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
1
|
+
require 'acts_as_textcaptcha/textcaptcha'
|
2
|
+
require 'acts_as_textcaptcha/textcaptcha_helper'
|
3
|
+
require "acts_as_textcaptcha/framework/rails#{Rails::VERSION::MAJOR}" if defined?(Rails)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
class Widget < ActiveRecord::Base
|
4
4
|
# uses textcaptcha.yml file for configuration
|
@@ -38,34 +38,42 @@ describe 'ActsAsTextcaptcha' do
|
|
38
38
|
|
39
39
|
before(:each) do
|
40
40
|
@note.generate_spam_question
|
41
|
-
@note.
|
41
|
+
@note.validate_textcaptcha.should be_false
|
42
|
+
@note.should_not be_valid
|
42
43
|
end
|
43
44
|
|
44
45
|
it 'should validate spam answer with possible answers' do
|
45
46
|
@note.spam_answer = '2'
|
46
|
-
@note.
|
47
|
+
@note.validate_textcaptcha.should be_true
|
48
|
+
@note.should be_valid
|
47
49
|
|
48
50
|
@note.spam_answer = 'two'
|
49
|
-
@note.
|
51
|
+
@note.validate_textcaptcha.should be_true
|
52
|
+
@note.should be_valid
|
50
53
|
|
51
54
|
@note.spam_answer = 'wrong'
|
52
|
-
@note.
|
55
|
+
@note.validate_textcaptcha.should be_false
|
56
|
+
@note.should_not be_valid
|
53
57
|
end
|
54
58
|
|
55
59
|
it 'should strip whitespace and downcase spam answer' do
|
56
60
|
@note.spam_answer = ' tWo '
|
57
|
-
@note.
|
61
|
+
@note.validate_textcaptcha.should be_true
|
62
|
+
@note.should be_valid
|
58
63
|
|
59
64
|
@note.spam_answer = ' 2 '
|
60
|
-
@note.
|
65
|
+
@note.validate_textcaptcha.should be_true
|
66
|
+
@note.should be_valid
|
61
67
|
end
|
62
68
|
|
63
69
|
it 'should always validate if not a new record' do
|
64
70
|
@note.spam_answer = '2'
|
65
71
|
@note.save!
|
66
72
|
@note.generate_spam_question
|
73
|
+
|
67
74
|
@note.new_record?.should be_false
|
68
|
-
@note.
|
75
|
+
@note.validate_textcaptcha.should be_true
|
76
|
+
@note.should be_valid
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
@@ -90,17 +98,20 @@ describe 'ActsAsTextcaptcha' do
|
|
90
98
|
|
91
99
|
it 'should always be valid if skip_spam_check? is true' do
|
92
100
|
@comment.generate_spam_question
|
93
|
-
@comment.
|
101
|
+
@comment.validate_textcaptcha.should be_false
|
102
|
+
@comment.should_not be_valid
|
103
|
+
|
94
104
|
@comment.stub!(:perform_spam_check?).and_return(false)
|
95
|
-
@comment.
|
105
|
+
@comment.validate_textcaptcha.should be_true
|
96
106
|
@comment.should be_valid
|
97
107
|
end
|
98
108
|
|
99
109
|
it 'should always fail validation if allowed? is false' do
|
100
|
-
@comment.
|
110
|
+
@comment.validate_textcaptcha.should be_true
|
101
111
|
@comment.stub!(:allowed?).and_return(false)
|
102
|
-
|
103
|
-
@comment.
|
112
|
+
|
113
|
+
@comment.validate_textcaptcha.should be_false
|
114
|
+
@comment.errors[:base].should eql(['Sorry, comments are currently disabled'])
|
104
115
|
@comment.should_not be_valid
|
105
116
|
end
|
106
117
|
end
|
@@ -109,8 +120,11 @@ describe 'ActsAsTextcaptcha' do
|
|
109
120
|
|
110
121
|
it 'should be configurable from inline options' do
|
111
122
|
@comment.textcaptcha_config.should eql({'api_key' => '8u5ixtdnq9csc84cok0owswgo'})
|
112
|
-
@review.textcaptcha_config.should
|
113
|
-
|
123
|
+
@review.textcaptcha_config.should eql({'bcrypt_cost'=>'3', 'questions'=>[{'question'=>'1+1', 'answers'=>'2,two'},
|
124
|
+
{'question'=>'The green hat is what color?', 'answers'=>'green'},
|
125
|
+
{'question'=>'Which is bigger: 67, 14 or 6', 'answers'=>'67,sixtyseven,sixty seven,sixty-seven'}],
|
126
|
+
'bcrypt_salt'=>'$2a$10$j0bmycH.SVfD1b5mpEGPpe', 'api_key'=>'8u5ixtdnq9csc84cok0owswgo'})
|
127
|
+
@note.textcaptcha_config.should eql({'questions'=>[{'question'=>'1+1', 'answers'=>'2,two'}]})
|
114
128
|
end
|
115
129
|
|
116
130
|
it 'should generate spam question from textcaptcha service' do
|
@@ -118,7 +132,9 @@ describe 'ActsAsTextcaptcha' do
|
|
118
132
|
@comment.spam_question.should_not be_nil
|
119
133
|
@comment.possible_answers.should_not be_nil
|
120
134
|
@comment.possible_answers.should be_an(Array)
|
121
|
-
|
135
|
+
|
136
|
+
@comment.validate_textcaptcha.should be_false
|
137
|
+
@comment.should_not be_valid
|
122
138
|
end
|
123
139
|
|
124
140
|
describe 'and textcaptcha unavailable' do
|
@@ -132,14 +148,17 @@ describe 'ActsAsTextcaptcha' do
|
|
132
148
|
@review.spam_question.should_not be_nil
|
133
149
|
@review.possible_answers.should_not be_nil
|
134
150
|
@review.possible_answers.should be_an(Array)
|
135
|
-
|
151
|
+
|
152
|
+
@review.validate_textcaptcha.should be_false
|
153
|
+
@review.should_not be_valid
|
136
154
|
end
|
137
155
|
|
138
156
|
it 'should not generate any spam question/answer if no user defined questions set' do
|
139
157
|
@comment.generate_spam_question
|
140
158
|
@comment.spam_question.should be_nil
|
141
159
|
@comment.possible_answers.should be_nil
|
142
|
-
@comment.
|
160
|
+
@comment.validate_textcaptcha.should be_true
|
161
|
+
@comment.should be_valid
|
143
162
|
end
|
144
163
|
end
|
145
164
|
end
|
@@ -162,7 +181,8 @@ describe 'ActsAsTextcaptcha' do
|
|
162
181
|
@widget.spam_question.should_not be_nil
|
163
182
|
@widget.possible_answers.should_not be_nil
|
164
183
|
@widget.possible_answers.should be_an(Array)
|
165
|
-
@widget.
|
184
|
+
@widget.validate_textcaptcha.should be_false
|
185
|
+
@widget.should_not be_valid
|
166
186
|
end
|
167
187
|
|
168
188
|
describe 'and textcaptcha unavailable' do
|
@@ -176,7 +196,8 @@ describe 'ActsAsTextcaptcha' do
|
|
176
196
|
@widget.spam_question.should_not be_nil
|
177
197
|
@widget.possible_answers.should_not be_nil
|
178
198
|
@widget.possible_answers.should be_an(Array)
|
179
|
-
@widget.
|
199
|
+
@widget.validate_textcaptcha.should be_false
|
200
|
+
@widget.should_not be_valid
|
180
201
|
end
|
181
202
|
end
|
182
203
|
end
|
data/spec/schema.rb
CHANGED
@@ -3,15 +3,15 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
3
3
|
create_table :widgets, :force => true do |t|
|
4
4
|
t.string :name
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
create_table :comments, :force => true do |t|
|
8
8
|
t.string :name
|
9
|
-
end
|
10
|
-
|
9
|
+
end
|
10
|
+
|
11
11
|
create_table :reviews, :force => true do |t|
|
12
12
|
t.string :name
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
create_table :notes, :force => true do |t|
|
16
16
|
t.string :name
|
17
17
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,13 @@
|
|
1
|
-
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
2
|
-
# from the project root directory.
|
3
1
|
ENV["RAILS_ENV"] ||= 'test'
|
2
|
+
|
4
3
|
require 'rubygems'
|
5
4
|
require 'spec'
|
6
5
|
require 'active_record'
|
7
6
|
require 'spec/autorun'
|
8
7
|
|
9
|
-
# Uncomment the next line to use webrat's matchers
|
10
|
-
#require 'webrat/integrations/rspec-rails'
|
11
|
-
|
12
|
-
# Requires supporting files with custom matchers and macros, etc,
|
13
|
-
# in ./support/ and its subdirectories.
|
14
|
-
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
15
|
-
|
16
8
|
require File.dirname(__FILE__) + '/../lib/acts_as_textcaptcha'
|
17
9
|
require File.dirname(__FILE__) + '/../init.rb'
|
18
10
|
|
19
|
-
|
20
|
-
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
11
|
+
ActiveRecord::Base.establish_connection(YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))[ENV['DB'] || 'sqlite3'])
|
21
12
|
|
22
13
|
load(File.dirname(__FILE__) + "/schema.rb")
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_textcaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
-
- 1
|
8
7
|
- 2
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 1
|
10
|
+
version: 2.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matthew Hutchinson
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-02 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -47,21 +47,15 @@ extra_rdoc_files:
|
|
47
47
|
- LICENSE
|
48
48
|
- README.rdoc
|
49
49
|
files:
|
50
|
-
- .
|
50
|
+
- lib/acts_as_textcaptcha.rb
|
51
|
+
- lib/acts_as_textcaptcha/framework/rails2.rb
|
52
|
+
- lib/acts_as_textcaptcha/framework/rails3.rb
|
53
|
+
- lib/acts_as_textcaptcha/textcaptcha.rb
|
54
|
+
- lib/acts_as_textcaptcha/textcaptcha_helper.rb
|
51
55
|
- LICENSE
|
52
56
|
- README.rdoc
|
53
|
-
- Rakefile
|
54
|
-
- VERSION
|
55
|
-
- acts_as_textcaptcha.gemspec
|
56
|
-
- config/textcaptcha.yml
|
57
|
-
- init.rb
|
58
|
-
- lib/acts_as_textcaptcha.rb
|
59
|
-
- lib/textcaptcha_helper.rb
|
60
|
-
- rails/init.rb
|
61
57
|
- spec/acts_as_textcaptcha_spec.rb
|
62
|
-
- spec/database.yml
|
63
58
|
- spec/schema.rb
|
64
|
-
- spec/spec.opts
|
65
59
|
- spec/spec_helper.rb
|
66
60
|
has_rdoc: true
|
67
61
|
homepage: http://github.com/matthutchinson/acts_as_textcaptcha
|
data/.gitignore
DELETED
data/Rakefile
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'rake'
|
2
|
-
require 'rake/rdoctask'
|
3
|
-
require 'spec/rake/spectask'
|
4
|
-
require 'spec'
|
5
|
-
|
6
|
-
desc 'Default: run spec tests.'
|
7
|
-
task :default => :spec
|
8
|
-
task :test => :spec
|
9
|
-
|
10
|
-
desc "Run all specs"
|
11
|
-
Spec::Rake::SpecTask.new(:spec) do |t|
|
12
|
-
t.spec_files = FileList['spec/*_spec.rb']
|
13
|
-
t.spec_opts = ['--options', 'spec/spec.opts']
|
14
|
-
end
|
15
|
-
|
16
|
-
desc "Run all specs with RCov"
|
17
|
-
Spec::Rake::SpecTask.new(:rcov) do |t|
|
18
|
-
t.spec_files = FileList['spec/*_spec.rb']
|
19
|
-
t.rcov = true
|
20
|
-
t.rcov_opts = ['--exclude', 'spec']
|
21
|
-
end
|
22
|
-
|
23
|
-
desc 'Generate documentation for the acts_as_textcaptcha plugin.'
|
24
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
25
|
-
rdoc.rdoc_dir = 'doc'
|
26
|
-
rdoc.title = 'acts_as_textcaptcha'
|
27
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
28
|
-
rdoc.rdoc_files.include('README.rdoc', 'LICENSE')
|
29
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
30
|
-
end
|
31
|
-
|
32
|
-
begin
|
33
|
-
require 'jeweler'
|
34
|
-
Jeweler::Tasks.new do |gemspec|
|
35
|
-
gemspec.name = "acts_as_textcaptcha"
|
36
|
-
gemspec.summary = "Spam protection for your models via logic questions and the excellent textcaptcha.com api"
|
37
|
-
gemspec.description = "Spam protection for your ActiveRecord models using logic questions and the excellent textcaptcha api. See textcaptcha.com for more details and to get your api key.
|
38
|
-
The logic questions are aimed at a child's age of 7, so can be solved easily by all but the most cognitively impaired users. As they involve human logic, such questions cannot be solved by a robot.
|
39
|
-
For more reasons on why logic questions are useful, see here; http://textcaptcha.com/why"
|
40
|
-
gemspec.email = "matt@hiddenloop.com"
|
41
|
-
gemspec.homepage = "http://github.com/matthutchinson/acts_as_textcaptcha"
|
42
|
-
gemspec.authors = ["Matthew Hutchinson"]
|
43
|
-
|
44
|
-
gemspec.add_dependency('bcrypt-ruby', '>= 2.1.2')
|
45
|
-
end
|
46
|
-
Jeweler::GemcutterTasks.new
|
47
|
-
rescue LoadError
|
48
|
-
end
|
49
|
-
|
50
|
-
begin
|
51
|
-
require 'metric_fu'
|
52
|
-
MetricFu::Configuration.run do |config|
|
53
|
-
config.metrics = [ :churn, :saikuro, :flog, :flay, :reek, :roodi, :rcov ]
|
54
|
-
config.graphs = [ :flog, :flay, :reek, :roodi, :rcov ]
|
55
|
-
config.flay = { :dirs_to_flay => ['lib'],
|
56
|
-
:minimum_score => 75 }
|
57
|
-
config.flog = { :dirs_to_flog => ['lib'] }
|
58
|
-
config.reek = { :dirs_to_reek => ['lib'] }
|
59
|
-
config.roodi = { :dirs_to_roodi => ['lib'] }
|
60
|
-
config.saikuro = { :output_directory => 'tmp/metric_fu/scratch/saikuro',
|
61
|
-
:input_directory => ['lib'],
|
62
|
-
:cyclo => "",
|
63
|
-
:filter_cyclo => "0",
|
64
|
-
:warn_cyclo => "5",
|
65
|
-
:error_cyclo => "7",
|
66
|
-
:formater => "text" }
|
67
|
-
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10 }
|
68
|
-
config.rcov = { :environment => 'test',
|
69
|
-
:test_files => ['spec/*_spec.rb'],
|
70
|
-
:rcov_opts => ["--sort coverage",
|
71
|
-
"--no-html",
|
72
|
-
"--text-coverage",
|
73
|
-
"--no-color",
|
74
|
-
"--profile",
|
75
|
-
"--rails",
|
76
|
-
"--exclude spec"]}
|
77
|
-
config.graph_engine = :bluff
|
78
|
-
end
|
79
|
-
rescue LoadError
|
80
|
-
puts "Metric Fu not available. Install it with: sudo gem install metric_fu reek roodi flay googlecharts"
|
81
|
-
end
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.2.1
|
data/acts_as_textcaptcha.gemspec
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{acts_as_textcaptcha}
|
8
|
-
s.version = "1.2.1"
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Matthew Hutchinson"]
|
12
|
-
s.date = %q{2010-07-29}
|
13
|
-
s.description = %q{Spam protection for your ActiveRecord models using logic questions and the excellent textcaptcha api. See textcaptcha.com for more details and to get your api key.
|
14
|
-
The logic questions are aimed at a child's age of 7, so can be solved easily by all but the most cognitively impaired users. As they involve human logic, such questions cannot be solved by a robot.
|
15
|
-
For more reasons on why logic questions are useful, see here; http://textcaptcha.com/why}
|
16
|
-
s.email = %q{matt@hiddenloop.com}
|
17
|
-
s.extra_rdoc_files = [
|
18
|
-
"LICENSE",
|
19
|
-
"README.rdoc"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".gitignore",
|
23
|
-
"LICENSE",
|
24
|
-
"README.rdoc",
|
25
|
-
"Rakefile",
|
26
|
-
"VERSION",
|
27
|
-
"acts_as_textcaptcha.gemspec",
|
28
|
-
"config/textcaptcha.yml",
|
29
|
-
"init.rb",
|
30
|
-
"lib/acts_as_textcaptcha.rb",
|
31
|
-
"lib/textcaptcha_helper.rb",
|
32
|
-
"rails/init.rb",
|
33
|
-
"spec/acts_as_textcaptcha_spec.rb",
|
34
|
-
"spec/database.yml",
|
35
|
-
"spec/schema.rb",
|
36
|
-
"spec/spec.opts",
|
37
|
-
"spec/spec_helper.rb"
|
38
|
-
]
|
39
|
-
s.homepage = %q{http://github.com/matthutchinson/acts_as_textcaptcha}
|
40
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
-
s.require_paths = ["lib"]
|
42
|
-
s.rubygems_version = %q{1.3.7}
|
43
|
-
s.summary = %q{Spam protection for your models via logic questions and the excellent textcaptcha.com api}
|
44
|
-
s.test_files = [
|
45
|
-
"spec/acts_as_textcaptcha_spec.rb",
|
46
|
-
"spec/schema.rb",
|
47
|
-
"spec/spec_helper.rb"
|
48
|
-
]
|
49
|
-
|
50
|
-
if s.respond_to? :specification_version then
|
51
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
-
s.specification_version = 3
|
53
|
-
|
54
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
|
-
s.add_runtime_dependency(%q<bcrypt-ruby>, [">= 2.1.2"])
|
56
|
-
else
|
57
|
-
s.add_dependency(%q<bcrypt-ruby>, [">= 2.1.2"])
|
58
|
-
end
|
59
|
-
else
|
60
|
-
s.add_dependency(%q<bcrypt-ruby>, [">= 2.1.2"])
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
data/config/textcaptcha.yml
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
development: &non_production_settings
|
2
|
-
api_key: 8u5ixtdnq9csc84cok0owswgo
|
3
|
-
# bcrypt can be used to encrypt valid possible answers in your session; http://bcrypt-ruby.rubyforge.org/
|
4
|
-
# (recommended if you are using cookie session storage)
|
5
|
-
# NOTE: bcrypt_salt must be a valid bcrypt salt; for security PLEASE CHANGE THIS, open irb and enter; require 'bcrypt'; BCrypt::Engine.generate_salt
|
6
|
-
bcrypt_salt: $2a$10$j0bmycH.SVfD1b5mpEGPpe
|
7
|
-
# an optional logarithmic var which determines how computational expensive the hash is to calculate (a cost of 4 is twice as much work as a cost of 3)
|
8
|
-
bcrypt_cost: 10 # default is 10, must be > 4 (large number means slower encryption)
|
9
|
-
# if you'd rather NOT use bcrypt, just remove these two settings, bcrypt_salt and bcrypt_cost, valid possible answers will be MD5 digested in your session
|
10
|
-
questions:
|
11
|
-
- question: 'Is ice hot or cold?'
|
12
|
-
answers: 'cold'
|
13
|
-
- question: 'what color is an orange?'
|
14
|
-
answers: 'orange'
|
15
|
-
- question: 'what is two plus 3?'
|
16
|
-
answers: '5,five'
|
17
|
-
- question: 'what is 5 times two?'
|
18
|
-
answers: '10,ten'
|
19
|
-
- question: 'How many colors in the list, green, brown, foot and blue?'
|
20
|
-
answers: '3,three'
|
21
|
-
- question: 'what is Georges name?'
|
22
|
-
answers: 'george'
|
23
|
-
- question: '11 minus 1?'
|
24
|
-
answers: '10,ten'
|
25
|
-
- question: 'is boiling water hot or cold?'
|
26
|
-
answers: 'hot'
|
27
|
-
- question: 'what color is my blue shirt today?'
|
28
|
-
answers: 'blue'
|
29
|
-
- question: 'what is 16 plus 4?'
|
30
|
-
answers: '20,twenty'
|
31
|
-
|
32
|
-
test:
|
33
|
-
*non_production_settings
|
34
|
-
|
35
|
-
production:
|
36
|
-
*non_production_settings
|
data/init.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/rails/init'
|
data/lib/textcaptcha_helper.rb
DELETED
data/rails/init.rb
DELETED
data/spec/database.yml
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
sqlite:
|
2
|
-
:adapter: sqlite
|
3
|
-
:database: acts_as_textcaptcha.sqlite.db
|
4
|
-
|
5
|
-
sqlite3:
|
6
|
-
:adapter: sqlite3
|
7
|
-
:database: acts_as_textcaptcha.sqlite3.db
|
8
|
-
|
9
|
-
postgresql:
|
10
|
-
:adapter: postgresql
|
11
|
-
:username: postgres
|
12
|
-
:password: postgres
|
13
|
-
:database: acts_as_textcaptcha_test
|
14
|
-
:min_messages: ERROR
|
15
|
-
|
16
|
-
mysql:
|
17
|
-
:adapter: mysql
|
18
|
-
:host: localhost
|
19
|
-
:username: root
|
20
|
-
:password:
|
21
|
-
:database: acts_as_textcaptcha_test
|
data/spec/spec.opts
DELETED