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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -0,0 +1 @@ | ||
| 1 | +# Install hook code here |
vendor/plugins/rails-math-captcha/lib/math_captcha/captcha.rb
0 → 100644
| @@ -0,0 +1,68 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 |