Commit 62bac6f22031a0166608bdd83cc0e1f98d553649
1 parent
7410bc19
Exists in
master
and in
22 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 | ... | ... |