Class: Turing::Challenge
- Inherits:
-
Object
- Object
- Turing::Challenge
- Defined in:
- lib/turing/challenge.rb
Overview
Captcha challenge generator and verifier
Purpose of this class is to provide abstraction layer (on top of PStore and Turing::Image) you can use to build Captcha challenge/response mechanism.
Example of use:
tc = Turing::Challenge.new(:store => 'store', :outdir => '.')
c = tc.generate_challenge
system("xv", c.file)
puts "Enter solution:"
r = $stdin.gets.chomp
if tc.valid_answer?(c.id, r)
puts "That's right."
else
puts "I don't think so."
end
In this example records about generated challenges are stored in file store
which is simple PStore. Images are generated via Turing::Image to current directory and then displayed via “xv” image viewer.
Defined Under Namespace
Classes: ChallengeObject, GeneratedChallenge
Instance Method Summary collapse
-
#generate_challenge ⇒ Object
Generate challenge (image containing random word from configured dictionary) and return
GeneratedChallenge
containingfile
(basename) andid
of this challenge. -
#initialize(opts = {}) ⇒ Challenge
constructor
Configure instance using options hash.
-
#valid_answer?(id, answer) ⇒ Boolean
Check if
answer
for challenge with givenid
is valid.
Constructor Details
#initialize(opts = {}) ⇒ Challenge
Configure instance using options hash.
Warning: Keys of this hash must be symbols.
Accepted options:
-
store
: File to be used as PStore for challenges. Default:$TMPDIR/turing-challenges.pstore
. -
dictionary
: Filename to be used as dictionary (base for random words). Default: gem’sshared/dictionary
file. -
lifetime
: Lifetime for generated challenge in seconds (to prevent “harvesting”). -
outdir
: Outdir for images generated by Turing::Image. Default:$TMPDIR
.
Given hash will be also used to initialize Turing::Image object.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/turing/challenge.rb', line 50 def initialize(opts = {}) # {{{ raise ArgumentError, "Opts must be hash!" unless opts.kind_of? Hash tmpdir = ENV["TMPDIR"] || '/tmp' base = File.join(File.dirname(__FILE__), '..', '..', 'shared') @options = { :store => File.join(tmpdir, 'turing-challenges.pstore'), :dictionary => File.join(base, 'dictionary'), :lifetime => 10*60, # 10 minutes :outdir => tmpdir, } @options.merge!(opts) begin @store = PStore.new(@options[:store]) rescue raise ArgumentError, "Failed to initialize store: #{$!}" end begin File.open(@options[:dictionary]) do |f| @dictionary = f.readlines.map! { |x| x.strip } end rescue raise ArgumentError, "Failed to load dictionary: #{$!}" end begin @ti = Turing::Image.new(@options) rescue raise ArgumentError, "Failed to initialize Turing::Image: #{$!}" end end |
Instance Method Details
#generate_challenge ⇒ Object
Generate challenge (image containing random word from configured dictionary) and return GeneratedChallenge
containing file
(basename) and id
of this challenge.
Generation of challenge is retried three times – to descrease possibility it will fail due to a bug in plugin. But if that happens, we just raise RuntimeError
.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/turing/challenge.rb', line 98 def generate_challenge # {{{ id = nil word = nil tries = 3 err = nil fname = nil begin id = random_id fname = id + ".jpg" word = @dictionary[rand(@dictionary.size)] @ti.generate(fname, word) rescue Object => err tries -= 1 retry if tries > 0 end raise "Failed to generate: #{err}" unless err.nil? begin @store.transaction do @store[id] = ChallengeObject.new(word, Time.now) end rescue raise "Failed to save to store: #{$!}" end GeneratedChallenge.new(fname, id) end |
#valid_answer?(id, answer) ⇒ Boolean
Check if answer
for challenge with given id
is valid.
Also removes image file and challenge from the store.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/turing/challenge.rb', line 130 def valid_answer?(id, answer) # {{{ ret = false begin @store.transaction do object = @store[id] # out if not found break if object.nil? # remove from store and delete img @store.delete(id) begin n = File.join(@options[:outdir], id + '.jpg') File.unlink(n) rescue Object end # true if it's ok if object.answer == answer && \ Time.now < object.when + (@options[:lifetime] || 0) ret = true end end rescue end ret end |