Commit 62bac6f22031a0166608bdd83cc0e1f98d553649
1 parent
7410bc19
Exists in
master
and in
23 other branches
Adding plugin
(ActionItem1732)
Showing
10 changed files
with
266 additions
and
0 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +Copyright (c) 2007 [name of plugin creator] | |
| 2 | + | |
| 3 | +Permission is hereby granted, free of charge, to any person obtaining | |
| 4 | +a copy of this software and associated documentation files (the | |
| 5 | +"Software"), to deal in the Software without restriction, including | |
| 6 | +without limitation the rights to use, copy, modify, merge, publish, | |
| 7 | +distribute, sublicense, and/or sell copies of the Software, and to | |
| 8 | +permit persons to whom the Software is furnished to do so, subject to | |
| 9 | +the following conditions: | |
| 10 | + | |
| 11 | +The above copyright notice and this permission notice shall be | |
| 12 | +included in all copies or substantial portions of the Software. | |
| 13 | + | |
| 14 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 15 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 16 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 17 | +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 18 | +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 19 | +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 20 | +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ... | ... |
| ... | ... | @@ -0,0 +1,45 @@ |
| 1 | +MathCaptcha | |
| 2 | +=========== | |
| 3 | + | |
| 4 | +TODO: Create form helpers | |
| 5 | + | |
| 6 | +Validates your Model to be saved by a human (or very clever script) by asking | |
| 7 | +the user to solve a very basic mathematical equation. | |
| 8 | + | |
| 9 | +It does not use the session to store the secret, but uses AES from EzCrypto. | |
| 10 | + | |
| 11 | + | |
| 12 | +Requirements | |
| 13 | +============ | |
| 14 | + * gem ezcrypto | |
| 15 | + * users who can calculate | |
| 16 | + | |
| 17 | +Usage | |
| 18 | +===== | |
| 19 | + | |
| 20 | +In your Model < ActiveRecord::Base | |
| 21 | + | |
| 22 | + has_captcha | |
| 23 | + | |
| 24 | + | |
| 25 | +In your form view: | |
| 26 | + | |
| 27 | + <%= @model.captcha.task %> = ? | |
| 28 | + <%= form.text_field :captcha_solution %> | |
| 29 | + <%= form.hidden_field :captcha_secret %> | |
| 30 | + | |
| 31 | + | |
| 32 | +If you want to skip the validation (in tests/specs), you can | |
| 33 | + | |
| 34 | + Model.skip_captcha! # or | |
| 35 | + @model.skip_captcha! | |
| 36 | + | |
| 37 | +and turn that off by | |
| 38 | + | |
| 39 | + Model.dont_skip_captcha! # or | |
| 40 | + @model.dont_skip_captcha! | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | +Copyright (c) 2007 Pat Nakajima, released under the MIT license | |
| 45 | +Copyright (c) 2009 Niklas Hofer, released under the MIT license | ... | ... |
| ... | ... | @@ -0,0 +1,22 @@ |
| 1 | +require 'rake' | |
| 2 | +require 'rake/testtask' | |
| 3 | +require 'rake/rdoctask' | |
| 4 | + | |
| 5 | +desc 'Default: run unit tests.' | |
| 6 | +task :default => :test | |
| 7 | + | |
| 8 | +desc 'Test the math_captcha plugin.' | |
| 9 | +Rake::TestTask.new(:test) do |t| | |
| 10 | + t.libs << 'lib' | |
| 11 | + t.pattern = 'test/**/*_test.rb' | |
| 12 | + t.verbose = true | |
| 13 | +end | |
| 14 | + | |
| 15 | +desc 'Generate documentation for the math_captcha plugin.' | |
| 16 | +Rake::RDocTask.new(:rdoc) do |rdoc| | |
| 17 | + rdoc.rdoc_dir = 'rdoc' | |
| 18 | + rdoc.title = 'MathCaptcha' | |
| 19 | + rdoc.options << '--line-numbers' << '--inline-source' | |
| 20 | + rdoc.rdoc_files.include('README') | |
| 21 | + rdoc.rdoc_files.include('lib/**/*.rb') | |
| 22 | +end | ... | ... |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +# Install hook code here | ... | ... |
vendor/plugins/rails-math-captcha/lib/math_captcha/captcha.rb
0 → 100644
| ... | ... | @@ -0,0 +1,68 @@ |
| 1 | +require 'rubygems' | |
| 2 | +require 'ezcrypto' | |
| 3 | +class Captcha | |
| 4 | + NUMBERS = (1..9).to_a | |
| 5 | + OPERATORS = [:+, :-, :*] | |
| 6 | + | |
| 7 | + attr_reader :x, :y, :operator | |
| 8 | + | |
| 9 | + def initialize(x=nil, y=nil, operator=nil) | |
| 10 | + @x = x || NUMBERS.sort_by{rand}.first | |
| 11 | + @y = y || NUMBERS.sort_by{rand}.first | |
| 12 | + @operator = operator || OPERATORS.sort_by{rand}.first | |
| 13 | + end | |
| 14 | + | |
| 15 | + # Only the #to_secret is shared with the client. | |
| 16 | + # It can be reused here to create the Captcha again | |
| 17 | + def self.from_secret(secret) | |
| 18 | + yml = cipher.decrypt64 secret | |
| 19 | + args = YAML.load(yml) | |
| 20 | + new(args[:x], args[:y], args[:operator]) | |
| 21 | + end | |
| 22 | + | |
| 23 | + def self.cipher | |
| 24 | + EzCrypto::Key.with_password key, 'bad_fixed_salt' | |
| 25 | + end | |
| 26 | + | |
| 27 | + def self.key | |
| 28 | + 'ultrasecret' | |
| 29 | + end | |
| 30 | + | |
| 31 | + | |
| 32 | + def check(answer) | |
| 33 | + answer == solution | |
| 34 | + end | |
| 35 | + | |
| 36 | + def task | |
| 37 | + "#{@x} #{@operator.to_s} #{@y}" | |
| 38 | + end | |
| 39 | + def task_with_questionmark | |
| 40 | + "#{@x} #{@operator.to_s} #{@y} = ?" | |
| 41 | + end | |
| 42 | + alias_method :to_s, :task | |
| 43 | + | |
| 44 | + def solution | |
| 45 | + @x.send @operator, @y | |
| 46 | + end | |
| 47 | + | |
| 48 | + def to_secret | |
| 49 | + cipher.encrypt64(to_yaml) | |
| 50 | + end | |
| 51 | + | |
| 52 | + def to_yaml | |
| 53 | + YAML::dump({ | |
| 54 | + :x => x, | |
| 55 | + :y => y, | |
| 56 | + :operator => operator | |
| 57 | + }) | |
| 58 | + end | |
| 59 | + | |
| 60 | + private | |
| 61 | + def cipher | |
| 62 | + @cipher ||= self.class.cipher | |
| 63 | + end | |
| 64 | + def reset_cipher | |
| 65 | + @cipher = nil | |
| 66 | + end | |
| 67 | + | |
| 68 | +end | ... | ... |
vendor/plugins/rails-math-captcha/lib/math_captcha/has_captcha.rb
0 → 100644
| ... | ... | @@ -0,0 +1,54 @@ |
| 1 | +module MathCaptcha | |
| 2 | + module HasCaptcha | |
| 3 | + module InstanceMethods | |
| 4 | + def must_solve_captcha | |
| 5 | + self.errors.add(:captcha_solution, "wrong answer.") unless self.captcha.check(self.captcha_solution.to_i) | |
| 6 | + end | |
| 7 | + def skip_captcha! | |
| 8 | + self.class.skip_captcha! | |
| 9 | + end | |
| 10 | + def skip_captcha? | |
| 11 | + self.class.skip_captcha? | |
| 12 | + end | |
| 13 | + def captcha | |
| 14 | + @captcha ||= Captcha.new | |
| 15 | + end | |
| 16 | + def captcha_secret=(secret) | |
| 17 | + @captcha = Captcha.from_secret(secret) | |
| 18 | + end | |
| 19 | + def captcha_secret | |
| 20 | + captcha.to_secret | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + module ClassMethods | |
| 25 | + def has_captcha | |
| 26 | + include InstanceMethods | |
| 27 | + attr_accessor :captcha_solution | |
| 28 | + dont_skip_captcha! | |
| 29 | + validates_presence_of :captcha_solution, | |
| 30 | + :on => :create, :message => "can't be blank", | |
| 31 | + :unless => Proc.new {|record| record.skip_captcha? } | |
| 32 | + validate_on_create :must_solve_captcha, | |
| 33 | + :unless => Proc.new {|record| record.skip_captcha? } | |
| 34 | + end | |
| 35 | + def skip_captcha! | |
| 36 | + @@skip_captcha = true | |
| 37 | + end | |
| 38 | + def dont_skip_captcha! | |
| 39 | + @@skip_captcha = false | |
| 40 | + end | |
| 41 | + def skip_captcha? | |
| 42 | + @@skip_captcha | |
| 43 | + end | |
| 44 | + def skipping_captcha(&block) | |
| 45 | + skipping_before = skip_captcha? | |
| 46 | + skip_captcha! | |
| 47 | + yield | |
| 48 | + dont_skip_captcha! if skipping_before | |
| 49 | + end | |
| 50 | + end | |
| 51 | + end | |
| 52 | +end | |
| 53 | + | |
| 54 | +ActiveRecord::Base.send(:extend, MathCaptcha::HasCaptcha::ClassMethods) | ... | ... |
| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | +require 'lib/math_captcha/captcha' | |
| 2 | +require 'base64' | |
| 3 | + | |
| 4 | +describe Captcha do | |
| 5 | + describe "with a random task" do | |
| 6 | + before(:each) do | |
| 7 | + @captcha = Captcha.new | |
| 8 | + end | |
| 9 | + it "should have arguments and an operator" do | |
| 10 | + @captcha.x.should_not be_nil | |
| 11 | + @captcha.y.should_not be_nil | |
| 12 | + @captcha.operator.should_not be_nil | |
| 13 | + end | |
| 14 | + it "should use numbers bigger than zero" do | |
| 15 | + @captcha.x.should > 0 | |
| 16 | + @captcha.y.should > 0 | |
| 17 | + end | |
| 18 | + it "should offer a human readable task" do | |
| 19 | + @captcha.task.should =~ /^\d+\s*[\+\-\*]\s*\d+$/ | |
| 20 | + end | |
| 21 | + it "should have a secret to use in forms" do | |
| 22 | + @captcha.to_secret.should_not be_nil | |
| 23 | + @captcha.to_secret.should_not be_empty | |
| 24 | + end | |
| 25 | + | |
| 26 | + it "should re-use its cipher" do | |
| 27 | + @captcha.send(:cipher).should == @captcha.send(:cipher) | |
| 28 | + end | |
| 29 | + | |
| 30 | + it "should have a base64 encoded secret" do | |
| 31 | + lambda { Base64.decode64(@captcha.to_secret).should_not be_nil }.should_not raise_error | |
| 32 | + end | |
| 33 | + | |
| 34 | + describe "re-creating another from secret" do | |
| 35 | + before(:each) do | |
| 36 | + @secret = @captcha.to_secret | |
| 37 | + @new_captcha = Captcha.from_secret(@secret) | |
| 38 | + end | |
| 39 | + it "should have the same arguments and operator" do | |
| 40 | + @new_captcha.x.should == @captcha.x | |
| 41 | + @new_captcha.y.should == @captcha.y | |
| 42 | + @new_captcha.operator.should == @captcha.operator | |
| 43 | + end | |
| 44 | + it "should have the same string" do | |
| 45 | + @new_captcha.task.should == @captcha.task | |
| 46 | + end | |
| 47 | + end | |
| 48 | + end | |
| 49 | +end | ... | ... |