text_captcha 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,89 @@
1
+ = TextCaptcha
2
+
3
+ A captcha verification for Rails apps, directly integrated into ActiveRecord.
4
+ It uses questions & answers instead of those ugly images.
5
+
6
+ == Installation
7
+
8
+ Just run
9
+
10
+ gem install text_captcha
11
+
12
+ == Usage
13
+
14
+ Just add the validation to your model.
15
+
16
+ class Comment < ActiveRecord::Base
17
+ validates_captcha
18
+ end
19
+
20
+ By default the <tt>{:on => :create}</tt> option will be used. You can provide
21
+ any other option you want.
22
+
23
+ class Comment < ActiveRecord::Base
24
+ validates_captcha :if => :new_record?
25
+ end
26
+
27
+ @comment = Comment.new
28
+
29
+ @comment.challenge
30
+ #=> The colour of a red T-shirt is?
31
+
32
+ @comment.challenge_answer = "red"
33
+ @comment.valid?
34
+ #=> true
35
+
36
+ Note that you can answer the question without worrying about uppercase/lowercase.
37
+ All strings are normalized before the comparison. So "ReD", "RED" or "red" will
38
+ pass the validation.
39
+
40
+ You can use TextCaptcha with a non-ActiveRecord class. You just need to
41
+ include the <tt>TextCaptcha::Validation</tt> module.
42
+
43
+ class Comment
44
+ include ActiveModel::Validations
45
+ include TextCaptcha::Validation
46
+
47
+ validates_captcha
48
+ end
49
+
50
+ @comment = Comment.new
51
+ @comment.valid?
52
+ #=> false
53
+
54
+ @comment.errors[:challenge_answer]
55
+ #=> ["You need to answer the anti-spam question."]
56
+
57
+ You can disable TextCaptcha without having to remove all validations.
58
+ This is specially good when running tests.
59
+
60
+ TextCaptcha.enabled = false
61
+
62
+ class Comment
63
+ include ActiveModel::Validations
64
+ include TextCaptcha::Validation
65
+
66
+ validates_captcha
67
+ end
68
+
69
+ @comment = Comment.new
70
+ @comment.valid?
71
+ #=> true
72
+
73
+ == To-Do
74
+
75
+ * Add more questions.
76
+
77
+ == License
78
+
79
+ (The MIT License)
80
+
81
+ Copyright © 2010:
82
+
83
+ * Nando Vieira - http://nandovieira.com.br
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
86
+
87
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require "rcov/rcovtask"
2
+ require "rake/testtask"
3
+ require "rake/rdoctask"
4
+ require "lib/text_captcha/version"
5
+
6
+ Rcov::RcovTask.new do |t|
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.rcov_opts = ["--sort coverage", "--exclude .gem"]
9
+
10
+ t.output_dir = "coverage"
11
+ t.libs << "test"
12
+ t.verbose = true
13
+ end
14
+
15
+ Rake::TestTask.new do |t|
16
+ t.libs << "lib"
17
+ t.libs << "test"
18
+ t.test_files = FileList["test/**/*_test.rb"]
19
+ t.verbose = true
20
+ t.ruby_opts = %w[-rubygems]
21
+ end
22
+
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.main = "README.rdoc"
25
+ rdoc.rdoc_dir = "doc"
26
+ rdoc.title = "TextCaptcha API"
27
+ rdoc.options += %w[ --line-numbers --inline-source --charset utf-8 ]
28
+ rdoc.rdoc_files.include("README.rdoc")
29
+ rdoc.rdoc_files.include("lib/**/*.rb")
30
+ end
31
+
32
+ begin
33
+ require "jeweler"
34
+
35
+ Jeweler::Tasks.new do |gem|
36
+ gem.name = "text_captcha"
37
+ gem.email = "fnando.vieira@gmail.com"
38
+ gem.homepage = "http://github.com/fnando/text_captcha"
39
+ gem.authors = ["Nando Vieira"]
40
+ gem.version = TextCaptcha::Version::STRING
41
+ gem.summary = "Simple captcha based on plain text questions."
42
+ gem.files = FileList["README.rdoc", "{lib,test,locales}/**/*", "Rakefile"]
43
+ end
44
+
45
+ Jeweler::GemcutterTasks.new
46
+ rescue LoadError => e
47
+ puts "You need to install jeweler to build this gem."
48
+ end
@@ -0,0 +1,33 @@
1
+ require "active_record"
2
+ require "active_support/all"
3
+
4
+ module TextCaptcha
5
+ autoload :Validation, "text_captcha/validation"
6
+ autoload :Version, "text_captcha/version"
7
+
8
+ class << self
9
+ # You can disable TextCaptcha without having to remove all validations.
10
+ # This is specially good when running tests.
11
+ #
12
+ # TextCaptcha.enabled = false
13
+ #
14
+ # class Comment
15
+ # include ActiveModel::Validations
16
+ # include TextCaptcha::Validation
17
+ #
18
+ # validates_captcha
19
+ # end
20
+ #
21
+ # @comment = Comment.new
22
+ # @comment.valid?
23
+ # #=> true
24
+ #
25
+ attr_accessor :enabled
26
+ end
27
+
28
+ # TextCaptcha is enabled by default
29
+ self.enabled = true
30
+
31
+ I18n.load_path += Dir[File.dirname(__FILE__) + "/../locales/**/*.yml"]
32
+ ::ActiveRecord::Base.send :include, TextCaptcha::Validation
33
+ end
@@ -0,0 +1,13 @@
1
+ module TextCaptcha
2
+ def self.configure(&block)
3
+ yield TextCaptcha::Config
4
+ end
5
+
6
+ class Config
7
+ class << self
8
+ attr_accessor :enabled
9
+ attr_accessor :challenge_attr
10
+ attr_accessor :answer_attr
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,124 @@
1
+ module TextCaptcha
2
+ module Validation
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include InstanceMethods
6
+ extend ClassMethods
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ # Validate if answer for a captcha question is correct.
12
+ # If there's no valid user, an error will be attached to
13
+ # <tt>:challenge_answer</tt> attribute.
14
+ #
15
+ # class Comment < ActiveRecord::Base
16
+ # validates_captcha
17
+ # end
18
+ #
19
+ # By default the <tt>{:on => :create}</tt> options will be used. You can provide
20
+ # any other option you want.
21
+ #
22
+ # class Comment < ActiveRecord::Base
23
+ # validates_captcha :if => :new_record?
24
+ # end
25
+ #
26
+ # @comment = Comment.new
27
+ #
28
+ # @comment.challenge
29
+ # #=> The colour of a red T-shirt is?
30
+ #
31
+ # @comment.challenge_answer = "red"
32
+ # @comment.valid?
33
+ # #=> true
34
+ #
35
+ # Note that you can answer the question without worrying about uppercase/lowercase.
36
+ # All strings are normalized before the comparison. So "ReD", "RED" or "red" will
37
+ # pass the validation.
38
+ #
39
+ # You can use TextCaptcha with a non-ActiveRecord class. You just need to
40
+ # include the <tt>TextCaptcha::Validation</tt> module.
41
+ #
42
+ # class Comment
43
+ # include ActiveModel::Validations
44
+ # include TextCaptcha::Validation
45
+ #
46
+ # validates_captcha
47
+ # end
48
+ #
49
+ # @comment = Comment.new
50
+ # @comment.valid?
51
+ # #=> false
52
+ #
53
+ # @comment.errors[:challenge_answer]
54
+ # #=> ["You need to answer the anti-spam question."]
55
+ #
56
+ #
57
+ def validates_captcha(options = {})
58
+ attr_accessor :challenge_answer
59
+ attr_writer :challenge_id
60
+
61
+ # Only add default options if class descends from ActiveRecord.
62
+ # Otherwise, validations won't run because regular classes don't
63
+ # have save new record status.
64
+ if self.ancestors.include?(::ActiveRecord::Base)
65
+ options.reverse_merge!(:on => :create)
66
+ end
67
+
68
+ validate :check_challenge_answer, options
69
+ end
70
+ end
71
+
72
+ module InstanceMethods
73
+ # Return an array with the current question and its answers.
74
+ def current_challenge
75
+ all_challenges[challenge_id.to_i]
76
+ end
77
+
78
+ # Return the question string.
79
+ def challenge
80
+ current_challenge.first
81
+ end
82
+
83
+ # Return all accepted answers.
84
+ def challenge_answers
85
+ current_challenge.last
86
+ end
87
+
88
+ # Return the question id. If none is assigned it chooses on
89
+ # randomly.
90
+ def challenge_id
91
+ @challenge_id ||= Kernel.rand(all_challenges.count)
92
+ end
93
+
94
+ # Return all questions.
95
+ def all_challenges
96
+ @all_challenges ||= I18n.t("text_captcha.challenges")
97
+ end
98
+
99
+ # Detect if the answer is correct. Will also return true if <tt>TextCaptcha.enabled</tt>
100
+ # is set to <tt>false</tt>.
101
+ def valid_challenge_answer?
102
+ return false unless current_challenge
103
+ return true unless TextCaptcha.enabled
104
+
105
+ answers = challenge_answers.collect {|a| to_captcha(a)}
106
+ !challenge_answer.blank? && answers.include?(to_captcha(challenge_answer))
107
+ end
108
+
109
+ private
110
+ # Check if the answer is correct. Add an error to
111
+ # <tt><:challenge_answer/tt> attribute otherwise.
112
+ def check_challenge_answer
113
+ unless valid_challenge_answer?
114
+ errors.add(:challenge_answer, I18n.t("text_captcha.error_message"))
115
+ end
116
+ end
117
+
118
+ # Normalize the strings for comparison.
119
+ def to_captcha(str)
120
+ str.to_s.squish.downcase
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,8 @@
1
+ module TextCaptcha
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 1
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,12 @@
1
+ en:
2
+ text_captcha:
3
+ error_message: "You need to answer the anti-spam question."
4
+ challenges:
5
+ - - "The colour of a red T-shirt is?"
6
+ - ["red"]
7
+ - - "What is two plus two?"
8
+ - ["4", "four"]
9
+ - - "Green, tracksuit or elephant: the colour is?"
10
+ - ["green"]
11
+ - - "The colour of a blue shark is?"
12
+ - ["blue"]
data/locales/pt.yml ADDED
@@ -0,0 +1,13 @@
1
+ pt: &PT
2
+ text_captcha:
3
+ error_message: "Você precisa responder a pergunta anti-spam corretamente."
4
+ challenges:
5
+ - - Quanto é 2 + 2?
6
+ - ["4", "quatro"]
7
+ - - Que dia vem depois de sábado?
8
+ - ["domingo"]
9
+ - - Quantas horas tem 1 dia?
10
+ - ["24"]
11
+
12
+ pt-BR:
13
+ <<: *PT
File without changes
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ gem "test-unit"
3
+ require "test/unit"
4
+ require "mocha"
5
+
6
+ require "text_captcha"
7
+
8
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
9
+ ActiveRecord::Schema.define(:version => 0) do
10
+ create_table :users do
11
+ end
12
+
13
+ create_table :accounts do
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ require "test_helper"
2
+
3
+ class User < ActiveRecord::Base
4
+ validates_captcha
5
+ end
6
+
7
+ class Account < ActiveRecord::Base
8
+ validates_captcha :on => :save
9
+ end
10
+
11
+ class Comment
12
+ include ActiveModel::Validations
13
+ include TextCaptcha::Validation
14
+
15
+ validates_captcha
16
+ end
17
+
18
+ class TextCaptchaTest < Test::Unit::TestCase
19
+ def setup
20
+ I18n.locale = :en
21
+ TextCaptcha.enabled = true
22
+ @user = User.new
23
+ @account = Account.new
24
+ end
25
+
26
+ def test_enabled_option
27
+ assert_respond_to TextCaptcha, :enabled
28
+ end
29
+
30
+ def test_skip_captcha_validation_when_is_disabled
31
+ TextCaptcha.enabled = false
32
+ assert @user.valid?
33
+ end
34
+
35
+ def test_return_all_challenges
36
+ assert_equal I18n.t("text_captcha.challenges"), @user.all_challenges
37
+ end
38
+
39
+ def test_randomly_choose_challenge_id
40
+ assert_kind_of Numeric, @user.challenge_id
41
+ end
42
+
43
+ def test_keep_previously_selected_challenge
44
+ id = @user.challenge_id
45
+ assert_equal id, @user.challenge_id
46
+ end
47
+
48
+ def test_select_a_new_challenge
49
+ Kernel.expects(:rand).twice.with(@user.all_challenges.count).returns(100, 101)
50
+
51
+ assert_equal 100, @user.challenge_id
52
+ @user.challenge_id = nil
53
+ assert_equal 101, @user.challenge_id
54
+ end
55
+
56
+ def test_return_challenge
57
+ assert_not_nil @user.challenge
58
+ end
59
+
60
+ def test_require_captcha_answer
61
+ assert !@user.valid?
62
+ assert_equal I18n.t("text_captcha.error_message"), @user.errors[:challenge_answer].to_s
63
+ end
64
+
65
+ def test_accept_valid_answer
66
+ @user.challenge_answer = @user.challenge_answers.first
67
+ assert @user.valid?
68
+ end
69
+
70
+ def test_respect_on_create_option
71
+ @user.challenge_answer = @user.challenge_answers.first
72
+ assert @user.save
73
+ assert @user.valid?
74
+ end
75
+
76
+ def test_respect_on_save_option
77
+ @account.challenge_answer = @account.challenge_answers.first
78
+ assert @account.save
79
+
80
+ @account.challenge_answer = nil
81
+ @account.challenge_id = nil
82
+
83
+ assert !@account.valid?
84
+ assert_equal I18n.t("text_captcha.error_message"), @account.errors[:challenge_answer].to_s
85
+ end
86
+
87
+ def test_extend_any_object
88
+ @comment = Comment.new
89
+ assert_not_nil @comment.challenge
90
+ assert !@comment.valid?
91
+ assert_equal I18n.t("text_captcha.error_message"), @comment.errors[:challenge_answer].to_s
92
+ end
93
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: text_captcha
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Nando Vieira
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-17 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: fnando.vieira@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.rdoc
30
+ files:
31
+ - README.rdoc
32
+ - Rakefile
33
+ - lib/text_captcha.rb
34
+ - lib/text_captcha/config.rb
35
+ - lib/text_captcha/validation.rb
36
+ - lib/text_captcha/version.rb
37
+ - locales/en.yml
38
+ - locales/pt.yml
39
+ - test/config_test.rb
40
+ - test/test_helper.rb
41
+ - test/text_captcha_test.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/fnando/text_captcha
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.7
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Simple captcha based on plain text questions.
76
+ test_files:
77
+ - test/config_test.rb
78
+ - test/test_helper.rb
79
+ - test/text_captcha_test.rb