nice_text_captcha 0.1.0

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 ADDED
@@ -0,0 +1,73 @@
1
+ Nice Text Captcha
2
+ by Neil Smith
3
+
4
+ About
5
+ -----
6
+
7
+ This generates text-based CATPCHAs that are "nice", by my own personal
8
+ subjective definition!
9
+
10
+ Questions are purposely simple, and examples would include:
11
+
12
+ * How many letters are there in the word 'White'?
13
+ * In the word "tiger", what is the letter in the 5th position?
14
+ * How many animals are in the words: "cat", "orange", and "dolphin"?
15
+ * What is 'four' + "nine"?
16
+
17
+ Implementation
18
+ --------------
19
+
20
+ You call a method in your view which both generates a random question, and places
21
+ the possible answers into a flash[] object, stored in a SHA2 hash.
22
+
23
+ (It's stored in a flash, as Rails handles ageing the data after a request - if we
24
+ used the session, it would hang around for the duration of the session.)
25
+
26
+ Then, in your controller action, you call a method which extracts the answers from
27
+ the flash, and injects them into your model.
28
+
29
+ A validation method on the model finally checks the answer against the hashed
30
+ possible answers, and pushes an error onto the base of your model if the answer
31
+ doesn't match.
32
+
33
+ For numerical answers ("What is 'two' plus 'three'?"), both numbers ("3") and
34
+ words ("three") are accepted.
35
+
36
+ Requirements
37
+ ------------
38
+
39
+ Tested against Rails 2.3 - currently untested against Rails 3.
40
+ 'linguistics' gem for turning numbers into words and ordinals.
41
+ 'Digest/SHA2' for hashing the possible answers.
42
+
43
+ Instructions
44
+ ------------
45
+
46
+ In your model, you need to add some validation:
47
+
48
+ class Thing < ActiveRecord::Base
49
+ validate :check_nice_text_captcha
50
+ end
51
+
52
+ In your controller, you need to add a call to populate the answers from the
53
+ data in the session:
54
+
55
+ class ThingsController < ActiveRecord::Base
56
+ def create
57
+ @thing = Thing.new(params[:thing]) # as normal
58
+ validate_nice_text_captcha_for(@thing)
59
+ end
60
+ end
61
+
62
+ Finally, in your view, you need to render the question by calling nice_text_captcha,
63
+ and give the user a text box for their answer:
64
+
65
+ <% form_for(@thing) do |f| %>
66
+ <!-- normal form elements here -->
67
+
68
+ <%= f.nice_text_captcha %><br />
69
+ <%= f.text_field :nice_text_captcha %><br />
70
+
71
+ <!-- probably a submit button here -->
72
+
73
+ <% end %>
@@ -0,0 +1,21 @@
1
+ #config.gem "linguistics"
2
+ require 'linguistics'
3
+ Linguistics::use(:en)
4
+
5
+ require 'nice_text_captcha/captcha'
6
+
7
+ require 'nice_text_captcha/active_record_extensions'
8
+ require 'nice_text_captcha/action_controller_extensions'
9
+ require 'nice_text_captcha/form_builder_extensions'
10
+
11
+ require 'nice_text_captcha/types/default_question'
12
+ require 'nice_text_captcha/types/letter_position_question'
13
+ require 'nice_text_captcha/types/maths_question'
14
+ require 'nice_text_captcha/types/word_length_question'
15
+ require 'nice_text_captcha/types/words_in_list_question'
16
+
17
+ ActionView::Helpers::FormBuilder.send :include, NiceTextCaptcha::FormBuilderExtensions
18
+
19
+ ActiveRecord::Base.send :include, NiceTextCaptcha::ActiveRecordExtensions
20
+
21
+ ActionController::Base.send :include, NiceTextCaptcha::ActionControllerExtensions
@@ -0,0 +1,11 @@
1
+ module NiceTextCaptcha
2
+
3
+ module ActionControllerExtensions
4
+
5
+ def validate_nice_text_captcha_for(obj)
6
+ obj.nice_text_captcha_responses = flash[:nice_text_captcha_responses] || ["no_answers_in_session"]
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,31 @@
1
+ module NiceTextCaptcha
2
+
3
+ module ActiveRecordExtensions
4
+
5
+ attr_accessor :nice_text_captcha
6
+ attr_accessor :nice_text_captcha_responses
7
+
8
+ def check_nice_text_captcha
9
+
10
+ captcha_successful = @nice_text_captcha_responses.nil?
11
+ @nice_text_captcha_responses.each do |response|
12
+ captcha_successful = true if Captcha.hash(self.class.to_s.underscore, @nice_text_captcha.strip.downcase) == response
13
+ end if @nice_text_captcha_responses
14
+
15
+ if !captcha_successful
16
+ errors.add_to_base(nice_text_captcha_failure_message)
17
+ end
18
+
19
+ @nice_text_captcha = nil
20
+
21
+ end
22
+
23
+ protected
24
+
25
+ def nice_text_captcha_failure_message
26
+ "Please ensure you answer the security question (CAPTCHA) correctly"
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,30 @@
1
+ module NiceTextCaptcha
2
+
3
+ class Captcha
4
+
5
+ attr_reader :question
6
+ attr_reader :answers
7
+
8
+ def initialize
9
+ captcha = [
10
+ Types::MathsQuestion,
11
+ Types::LetterPositionQuestion,
12
+ Types::WordsInListQuestion,
13
+ Types::WordLengthQuestion,
14
+ ].rand.new
15
+ @question = captcha.question
16
+ @answers = captcha.answers
17
+ end
18
+
19
+ def encrypted_answers_for(object_name)
20
+ answers.collect { |answer| Captcha.hash(object_name, answer) }
21
+ end
22
+
23
+ def self.hash(obj, answer)
24
+ str = obj.to_s + answer.to_s + ActionController::Base.session_options[:secret].to_s
25
+ Digest::SHA2.hexdigest(str)
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,13 @@
1
+ module NiceTextCaptcha
2
+
3
+ module FormBuilderExtensions
4
+
5
+ def nice_text_captcha
6
+ captcha = NiceTextCaptcha::Captcha.new
7
+ @template.flash[:nice_text_captcha_responses] = captcha.encrypted_answers_for(@object_name)
8
+ captcha.question
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,42 @@
1
+ module NiceTextCaptcha::Types
2
+ class DefaultQuestion
3
+
4
+ ANIMALS = ["dog", "cat", "dolphin", "mouse", "horse", "penguin", "panda", "tiger", "lion", "zebra"]
5
+ COLOURS = ["black", "white", "red", "green", "blue", "pink", "purple", "orange", "yellow", "brown"]
6
+
7
+ attr_reader :question
8
+ attr_reader :answers
9
+
10
+ private
11
+
12
+ def fifty_fifty?
13
+ rand(2) == 0
14
+ end
15
+
16
+ def number_to_word(number)
17
+ number.to_i.en.numwords
18
+ end
19
+
20
+ def number_to_ordinal(number)
21
+ number.to_i.en.ordinal
22
+ end
23
+
24
+ def randomly_quote(word)
25
+ q = ["'", "\""].rand
26
+ q + word + q
27
+ end
28
+
29
+ def randomly_capitalise(word)
30
+ fifty_fifty? ? word : word.capitalize
31
+ end
32
+
33
+ def randomly_quote_and_capitalise(word)
34
+ randomly_quote(randomly_capitalise(word))
35
+ end
36
+
37
+ def randomly_sort(list)
38
+ list.sort { |x,y| rand(100) <=> rand(100) }
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ class NiceTextCaptcha::Types::LetterPositionQuestion < NiceTextCaptcha::Types::DefaultQuestion
2
+
3
+ def initialize
4
+ word = [ANIMALS + COLOURS].flatten.rand
5
+ position = rand(word.length) + 1
6
+ answer = word[position - 1, 1]
7
+
8
+ position_string = number_to_ordinal(position)
9
+
10
+ position_string = "first" if position == 1 and fifty_fifty?
11
+ position_string = "second" if position == 2 and fifty_fifty?
12
+ position_string = (fifty_fifty? ? "final" : "last") if position == word.length and fifty_fifty?
13
+ position_string = "second to last" if position == word.length - 1 and fifty_fifty?
14
+
15
+ @question = format_question(randomly_quote_and_capitalise(word), position_string)
16
+ @answers = [answer]
17
+ end
18
+
19
+ def format_question(word, position)
20
+ [
21
+ "What is the %s letter in the word %s?" % [position, word],
22
+ "What's the %s letter in the word %s?" % [position, word],
23
+ "The %s letter in %s is what?" % [position, word],
24
+ "The %s letter in the word %s is?" % [position, word],
25
+ "In the word %s, what is the %s letter?" % [word, position],
26
+ "In the word %s, what is the letter in the %s position?" % [word, position],
27
+ ].rand
28
+ end
29
+
30
+ private
31
+
32
+ end
@@ -0,0 +1,36 @@
1
+ class NiceTextCaptcha::Types::MathsQuestion < NiceTextCaptcha::Types::DefaultQuestion
2
+
3
+ def initialize
4
+ number_1 = one_to_nine
5
+ number_2 = one_to_nine
6
+ answer = number_1 + number_2
7
+
8
+ @question = format_question(number_or_word(number_1), number_or_word(number_2))
9
+ @answers = [answer, number_to_word(answer)]
10
+ end
11
+
12
+ def format_question(one, two)
13
+ [
14
+ "What is %s plus %s?" % [one, two],
15
+ "What is %s + %s?" % [one, two],
16
+ "What is %s added to %s?" % [one, two],
17
+ "What do you get if you add the numbers %s and %s?" % [one, two],
18
+ "What is the result of adding %s and %s?" % [one, two],
19
+ ].rand
20
+ end
21
+
22
+ private
23
+
24
+ def one_to_nine
25
+ rand(9) + 1
26
+ end
27
+
28
+ def number_or_word(number)
29
+ if fifty_fifty?
30
+ number
31
+ else
32
+ randomly_quote_and_capitalise(number_to_word(number))
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,20 @@
1
+ class NiceTextCaptcha::Types::WordLengthQuestion < NiceTextCaptcha::Types::DefaultQuestion
2
+
3
+ def initialize
4
+ word = [ANIMALS + COLOURS].flatten.rand
5
+ answer = word.length
6
+
7
+ @question = format_question(randomly_quote_and_capitalise(word))
8
+ @answers = [answer, number_to_word(answer)]
9
+ end
10
+
11
+ def format_question(word)
12
+ [
13
+ "How many letters are in the word %s?" % word,
14
+ "How many letters are there in the word %s?" % word,
15
+ "How many letters are there in %s?" % word,
16
+ "In the word %s, how many letters are there?" % word,
17
+ ].rand
18
+ end
19
+
20
+ end
@@ -0,0 +1,38 @@
1
+ class NiceTextCaptcha::Types::WordsInListQuestion < NiceTextCaptcha::Types::DefaultQuestion
2
+
3
+ def initialize
4
+ animals = randomly_sort(ANIMALS)
5
+ colours = randomly_sort(COLOURS)
6
+
7
+ answer = rand(3) + 1
8
+
9
+ if fifty_fifty?
10
+ words = animals[0 .. answer-1]
11
+ words << colours[0 .. rand(3)]
12
+ noun = "animals"
13
+ else
14
+ words = colours[0 .. answer-1]
15
+ words << animals[0 .. rand(3)]
16
+ noun = "colours"
17
+ end
18
+
19
+ words.flatten!
20
+ words.collect! { |w| randomly_quote_and_capitalise(w) }
21
+
22
+ @question = format_question(noun, randomly_sort(words))
23
+
24
+ @answers = [answer, number_to_word(answer)]
25
+ end
26
+
27
+ def format_question(noun, word_list)
28
+ collective_noun = ["list", "words", "list of words", "word list"].rand
29
+ collective_noun = "following " + collective_noun if fifty_fifty?
30
+
31
+ [
32
+ "How many %s are in the %s: %s?" % [noun, collective_noun, word_list.to_sentence],
33
+ "How many %s are there in the %s: %s?" % [noun, collective_noun, word_list.to_sentence],
34
+ "In the %s: %s - how many %s are there?" % [collective_noun, word_list.to_sentence, noun],
35
+ ].rand
36
+ end
37
+
38
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nice_text_captcha
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Neil Smith
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-06 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: neil.jx.smith@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - README
32
+ - lib/nice_text_captcha/action_controller_extensions.rb
33
+ - lib/nice_text_captcha/active_record_extensions.rb
34
+ - lib/nice_text_captcha/captcha.rb
35
+ - lib/nice_text_captcha/form_builder_extensions.rb
36
+ - lib/nice_text_captcha/types/default_question.rb
37
+ - lib/nice_text_captcha/types/letter_position_question.rb
38
+ - lib/nice_text_captcha/types/maths_question.rb
39
+ - lib/nice_text_captcha/types/word_length_question.rb
40
+ - lib/nice_text_captcha/types/words_in_list_question.rb
41
+ - lib/nice_text_captcha.rb
42
+ has_rdoc: true
43
+ homepage:
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --main
49
+ - README
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.7
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Generates Text Catpchas, that are 'nice' by my own personal definition!
77
+ test_files: []
78
+