validates_captcha 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/Rakefile +9 -9
- data/lib/validates_captcha.rb +12 -12
- data/lib/validates_captcha/controller_validation.rb +11 -10
- data/lib/validates_captcha/form_builder.rb +2 -2
- data/lib/validates_captcha/form_helper.rb +11 -11
- data/lib/validates_captcha/image_generator/simple.rb +24 -23
- data/lib/validates_captcha/model_validation.rb +29 -13
- data/lib/validates_captcha/provider/dynamic_image.rb +50 -50
- data/lib/validates_captcha/provider/question.rb +28 -27
- data/lib/validates_captcha/provider/static_image.rb +62 -62
- data/lib/validates_captcha/string_generator/simple.rb +18 -17
- data/lib/validates_captcha/symmetric_encryptor/simple.rb +11 -10
- data/lib/validates_captcha/test_case.rb +1 -0
- data/lib/validates_captcha/version.rb +3 -2
- data/rails/init.rb +4 -4
- data/tasks/static_image_tasks.rake +7 -9
- data/test/cases/controller_validation_test.rb +42 -41
- data/test/cases/image_generator/simple_test.rb +8 -8
- data/test/cases/model_validation_test.rb +79 -64
- data/test/cases/provider/dynamic_image_test.rb +29 -28
- data/test/cases/provider/question_test.rb +11 -10
- data/test/cases/provider/static_image_test.rb +40 -39
- data/test/cases/string_generator/simple_test.rb +29 -29
- data/test/cases/symmetric_encryptor/simple_test.rb +6 -6
- data/test/cases/validates_captcha_test.rb +7 -6
- data/test/test_helper.rb +4 -0
- metadata +3 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 0.9.6 (October 30, 2009)
|
2
|
+
|
3
|
+
* Do not make captcha_challenge and captcha_solution attr_accessible. Instead overwrite AR#attributes= so they can be mass assigned.
|
4
|
+
|
5
|
+
|
1
6
|
== 0.9.5 (October 9, 2009)
|
2
7
|
|
3
8
|
* API change: renamed Provider::Image to Provider::DynamicImage in order to guard against confusion
|
@@ -20,3 +25,4 @@
|
|
20
25
|
== 0.9.2 (September 27, 2009)
|
21
26
|
|
22
27
|
* Initial version
|
28
|
+
|
data/Rakefile
CHANGED
@@ -29,10 +29,10 @@ Rake::RDocTask.new do |rdoc|
|
|
29
29
|
rdoc.rdoc_dir = 'doc'
|
30
30
|
rdoc.title = "Validates Captcha"
|
31
31
|
rdoc.main = "README.rdoc"
|
32
|
-
|
32
|
+
|
33
33
|
rdoc.options << '--line-numbers' << '--inline-source'
|
34
34
|
rdoc.options << '--charset' << 'utf-8'
|
35
|
-
|
35
|
+
|
36
36
|
rdoc.rdoc_files.include 'README.rdoc'
|
37
37
|
rdoc.rdoc_files.include 'MIT-LICENSE'
|
38
38
|
rdoc.rdoc_files.include 'CHANGELOG.rdoc'
|
@@ -52,18 +52,18 @@ end
|
|
52
52
|
|
53
53
|
desc 'Run tests by default'
|
54
54
|
task :default => :test
|
55
|
-
|
55
|
+
|
56
56
|
Rake::TestTask.new do |t|
|
57
57
|
t.libs << 'test'
|
58
58
|
t.test_files = FileList['test/**/*_test.rb']
|
59
59
|
#t.verbose = true
|
60
60
|
#t.warning = true
|
61
61
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
|
63
|
+
|
64
|
+
|
65
65
|
spec = eval(File.read('validates_captcha.gemspec'))
|
66
|
-
|
66
|
+
|
67
67
|
Rake::GemPackageTask.new(spec) do |pkg|
|
68
68
|
pkg.gem_spec = spec
|
69
69
|
pkg.need_tar = true
|
@@ -76,9 +76,9 @@ desc 'Publish the release files to RubyForge'
|
|
76
76
|
task :release => [:package] do
|
77
77
|
require 'rubyforge'
|
78
78
|
require 'rake/contrib/rubyforgepublisher'
|
79
|
-
|
79
|
+
|
80
80
|
packages = %w(gem tgz zip).collect { |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
81
|
-
|
81
|
+
|
82
82
|
rubyforge = RubyForge.new
|
83
83
|
rubyforge.configure
|
84
84
|
rubyforge.add_release RUBY_FORGE_PROJECT, RUBY_FORGE_PROJECT, RELEASE_NAME, *packages
|
data/lib/validates_captcha.rb
CHANGED
@@ -22,10 +22,10 @@
|
|
22
22
|
#++
|
23
23
|
|
24
24
|
|
25
|
-
# This module contains the getter and setter for the captcha provider.
|
26
|
-
# This allows you to replace it with your custom implementation. For more
|
27
|
-
# information on how to bring Validates Captcha to use your own
|
28
|
-
# implementation instead of the default one, consult the documentation
|
25
|
+
# This module contains the getter and setter for the captcha provider.
|
26
|
+
# This allows you to replace it with your custom implementation. For more
|
27
|
+
# information on how to bring Validates Captcha to use your own
|
28
|
+
# implementation instead of the default one, consult the documentation
|
29
29
|
# for the default provider.
|
30
30
|
module ValidatesCaptcha
|
31
31
|
autoload :ModelValidation, 'validates_captcha/model_validation'
|
@@ -34,34 +34,34 @@ module ValidatesCaptcha
|
|
34
34
|
autoload :FormBuilder, 'validates_captcha/form_builder'
|
35
35
|
autoload :TestCase, 'validates_captcha/test_case'
|
36
36
|
autoload :VERSION, 'validates_captcha/version'
|
37
|
-
|
37
|
+
|
38
38
|
module Provider
|
39
39
|
autoload :Question, 'validates_captcha/provider/question'
|
40
40
|
autoload :DynamicImage, 'validates_captcha/provider/dynamic_image'
|
41
41
|
autoload :StaticImage, 'validates_captcha/provider/static_image'
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
module StringGenerator
|
45
45
|
autoload :Simple, 'validates_captcha/string_generator/simple'
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
module SymmetricEncryptor
|
49
49
|
autoload :Simple, 'validates_captcha/symmetric_encryptor/simple'
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
module ImageGenerator
|
53
53
|
autoload :Simple, 'validates_captcha/image_generator/simple'
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
@@provider = nil
|
57
|
-
|
57
|
+
|
58
58
|
class << self
|
59
59
|
# Returns Validates Captcha's current version number.
|
60
60
|
def version
|
61
61
|
ValidatesCaptcha::VERSION::STRING
|
62
62
|
end
|
63
|
-
|
64
|
-
# Returns the current captcha challenge provider. Defaults to an instance of
|
63
|
+
|
64
|
+
# Returns the current captcha challenge provider. Defaults to an instance of
|
65
65
|
# the ValidatesCaptcha::Provider::Question class.
|
66
66
|
def provider
|
67
67
|
@@provider ||= Provider::Question.new
|
@@ -3,12 +3,12 @@ module ValidatesCaptcha
|
|
3
3
|
def self.included(base) #:nodoc:
|
4
4
|
base.extend ClassMethods
|
5
5
|
end
|
6
|
-
|
7
|
-
# This module extends ActionController::Base with methods for captcha
|
6
|
+
|
7
|
+
# This module extends ActionController::Base with methods for captcha
|
8
8
|
# verification.
|
9
|
-
module ClassMethods
|
10
|
-
# This method is the one Validates Captcha got its name from. It
|
11
|
-
# internally calls #validates_captcha_of with the name of the controller
|
9
|
+
module ClassMethods
|
10
|
+
# This method is the one Validates Captcha got its name from. It
|
11
|
+
# internally calls #validates_captcha_of with the name of the controller
|
12
12
|
# as first argument and passing the conditions hash.
|
13
13
|
#
|
14
14
|
# Usage Example:
|
@@ -26,13 +26,13 @@ module ValidatesCaptcha
|
|
26
26
|
def validates_captcha(conditions = {})
|
27
27
|
validates_captcha_of controller_name, conditions
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Activates captcha validation for the specified model.
|
31
31
|
#
|
32
32
|
# The +model+ argument can be a Class, a string, or a symbol.
|
33
33
|
#
|
34
|
-
# This method internally creates an around filter, passing the
|
35
|
-
# +conditions+ argument to it. So you can (de)activate captcha
|
34
|
+
# This method internally creates an around filter, passing the
|
35
|
+
# +conditions+ argument to it. So you can (de)activate captcha
|
36
36
|
# validation for specific actions.
|
37
37
|
#
|
38
38
|
# Usage examples:
|
@@ -43,11 +43,11 @@ module ValidatesCaptcha
|
|
43
43
|
# validates_captcha_of 'user', :except => :persist
|
44
44
|
#
|
45
45
|
# # ... actions go here ...
|
46
|
-
# end
|
46
|
+
# end
|
47
47
|
def validates_captcha_of(model, conditions = {})
|
48
48
|
model = model.is_a?(Class) ? model : model.to_s.classify.constantize
|
49
49
|
without_formats = Array.wrap(conditions.delete(:without)).map(&:to_sym)
|
50
|
-
|
50
|
+
|
51
51
|
around_filter(conditions) do |controller, action|
|
52
52
|
if without_formats.include?(controller.request.format.to_sym)
|
53
53
|
action.call
|
@@ -61,3 +61,4 @@ module ValidatesCaptcha
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
@@ -3,11 +3,11 @@ module ValidatesCaptcha
|
|
3
3
|
def captcha_challenge(options = {}) #:nodoc:
|
4
4
|
@template.captcha_challenge @object_name, options.merge(:object => @object)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
def captcha_field(options = {}) #:nodoc:
|
8
8
|
@template.captcha_field @object_name, options.merge(:object => @object)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def regenerate_captcha_challenge_link(options = {}, html_options = {}) #:nodoc:
|
12
12
|
@template.regenerate_captcha_challenge_link @object_name, options.merge(:object => @object), html_options
|
13
13
|
end
|
@@ -1,37 +1,37 @@
|
|
1
1
|
module ValidatesCaptcha
|
2
2
|
module FormHelper
|
3
|
-
# Returns the captcha challenge.
|
3
|
+
# Returns the captcha challenge.
|
4
4
|
#
|
5
5
|
# Internally calls the +render_challenge+ method of ValidatesCaptcha#provider.
|
6
6
|
def captcha_challenge(object_name, options = {})
|
7
7
|
options.symbolize_keys!
|
8
|
-
|
8
|
+
|
9
9
|
object = options.delete(:object)
|
10
10
|
sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
11
|
-
|
11
|
+
|
12
12
|
ValidatesCaptcha.provider.render_challenge sanitized_object_name, object, options
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# Returns an input tag of the "text" type tailored for entering the captcha solution.
|
16
16
|
#
|
17
|
-
# Internally calls Rails' #text_field helper method, passing the +object_name+ and
|
17
|
+
# Internally calls Rails' #text_field helper method, passing the +object_name+ and
|
18
18
|
# +options+ arguments.
|
19
19
|
def captcha_field(object_name, options = {})
|
20
20
|
options.delete(:id)
|
21
|
-
|
21
|
+
|
22
22
|
hidden_field(object_name, :captcha_challenge, options) + text_field(object_name, :captcha_solution, options)
|
23
23
|
end
|
24
|
-
|
25
|
-
# By default, returns an anchor tag that makes an AJAX request to fetch a new captcha challenge and updates
|
24
|
+
|
25
|
+
# By default, returns an anchor tag that makes an AJAX request to fetch a new captcha challenge and updates
|
26
26
|
# the current challenge after the request is complete.
|
27
|
-
#
|
27
|
+
#
|
28
28
|
# Internally calls +render_regenerate_challenge_link+ method of ValidatesCaptcha#provider.
|
29
29
|
def regenerate_captcha_challenge_link(object_name, options = {}, html_options = {})
|
30
30
|
options.symbolize_keys!
|
31
|
-
|
31
|
+
|
32
32
|
object = options.delete(:object)
|
33
33
|
sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
34
|
-
|
34
|
+
|
35
35
|
ValidatesCaptcha.provider.render_regenerate_challenge_link sanitized_object_name, object, options, html_options
|
36
36
|
end
|
37
37
|
end
|
@@ -1,24 +1,24 @@
|
|
1
1
|
module ValidatesCaptcha
|
2
2
|
module ImageGenerator
|
3
|
-
# This class is responsible for creating the captcha image. It internally
|
4
|
-
# uses ImageMagick's +convert+ command to generate the image bytes. So
|
3
|
+
# This class is responsible for creating the captcha image. It internally
|
4
|
+
# uses ImageMagick's +convert+ command to generate the image bytes. So
|
5
5
|
# ImageMagick must be installed on the system for it to work properly.
|
6
6
|
#
|
7
|
-
# In order to deliver the captcha image to the user's browser,
|
8
|
-
# Validate Captcha's Rack middleware calls the methods of this class
|
9
|
-
# to create the image, to retrieve its mime type, and to construct the
|
7
|
+
# In order to deliver the captcha image to the user's browser,
|
8
|
+
# Validate Captcha's Rack middleware calls the methods of this class
|
9
|
+
# to create the image, to retrieve its mime type, and to construct the
|
10
10
|
# path to it.
|
11
11
|
#
|
12
|
-
# The image generation process is no rocket science. The chars are just
|
13
|
-
# laid out next to each other with varying vertical positions, font sizes,
|
14
|
-
# and weights. Then a slight rotation is performed and some randomly
|
12
|
+
# The image generation process is no rocket science. The chars are just
|
13
|
+
# laid out next to each other with varying vertical positions, font sizes,
|
14
|
+
# and weights. Then a slight rotation is performed and some randomly
|
15
15
|
# positioned lines are rendered on the canvas.
|
16
16
|
#
|
17
|
-
# Sure, the created captcha can easily be cracked by intelligent
|
18
|
-
# bots. As the name of the class suggests, it's rather a starting point
|
17
|
+
# Sure, the created captcha can easily be cracked by intelligent
|
18
|
+
# bots. As the name of the class suggests, it's rather a starting point
|
19
19
|
# for your own implementations.
|
20
20
|
#
|
21
|
-
# You can implement your own (better) image generator by creating a
|
21
|
+
# You can implement your own (better) image generator by creating a
|
22
22
|
# class that conforms to the method definitions of the example below.
|
23
23
|
#
|
24
24
|
# Example for a custom image generator:
|
@@ -48,43 +48,43 @@ module ValidatesCaptcha
|
|
48
48
|
#
|
49
49
|
# ValidatesCaptcha::Provider::StaticImage.image_generator = AdvancedImageGenerator.new
|
50
50
|
# ValidatesCaptcha.provider = ValidatesCaptcha::Provider::StaticImage.new
|
51
|
-
#
|
51
|
+
#
|
52
52
|
class Simple
|
53
53
|
MIME_TYPE = 'image/gif'.freeze
|
54
54
|
FILE_EXTENSION = '.gif'.freeze
|
55
|
-
|
56
|
-
# Returns a string containing the image bytes of the captcha.
|
55
|
+
|
56
|
+
# Returns a string containing the image bytes of the captcha.
|
57
57
|
# As the only argument, the cleartext captcha text must be passed.
|
58
58
|
def generate(captcha_code)
|
59
59
|
image_width = captcha_code.length * 20 + 10
|
60
|
-
|
60
|
+
|
61
61
|
cmd = []
|
62
62
|
cmd << "convert -size #{image_width}x40 xc:grey84 -background grey84 -fill black "
|
63
|
-
|
63
|
+
|
64
64
|
captcha_code.split(//).each_with_index do |char, i|
|
65
65
|
cmd << " -pointsize #{rand(8) + 15} "
|
66
66
|
cmd << " -weight #{rand(2) == 0 ? '4' : '8'}00 "
|
67
67
|
cmd << " -draw 'text #{5 + 20 * i},#{rand(10) + 20} \"#{char}\"' "
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
cmd << " -rotate #{rand(2) == 0 ? '-' : ''}5 -fill grey40 "
|
71
|
-
|
71
|
+
|
72
72
|
captcha_code.size.times do
|
73
73
|
cmd << " -draw 'line #{rand(image_width)},0 #{rand(image_width)},60' "
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
cmd << " gif:-"
|
77
|
-
|
77
|
+
|
78
78
|
image_magick_command = cmd.join
|
79
|
-
|
79
|
+
|
80
80
|
`#{image_magick_command}`
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
# Returns the image mime type. This is always 'image/gif'.
|
84
84
|
def mime_type
|
85
85
|
MIME_TYPE
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
# Returns the image file extension. This is always '.gif'.
|
89
89
|
def file_extension
|
90
90
|
FILE_EXTENSION
|
@@ -92,3 +92,4 @@ module ValidatesCaptcha
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
+
|
@@ -3,18 +3,19 @@ module ValidatesCaptcha
|
|
3
3
|
def self.included(base) #:nodoc:
|
4
4
|
base.extend ClassMethods
|
5
5
|
base.send :include, InstanceMethods
|
6
|
-
|
6
|
+
|
7
7
|
base.class_eval do
|
8
|
-
attr_accessible :captcha_challenge, :captcha_solution
|
9
8
|
attr_accessor :captcha_solution
|
10
9
|
attr_writer :captcha_challenge
|
11
|
-
|
10
|
+
|
11
|
+
alias_method_chain :attributes=, :captcha_fields
|
12
|
+
|
12
13
|
validate :validate_captcha, :if => :validate_captcha?
|
13
14
|
end
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
module ClassMethods
|
17
|
-
# Activates captcha validation on entering the block and deactivates
|
18
|
+
# Activates captcha validation on entering the block and deactivates
|
18
19
|
# captcha validation on leaving the block.
|
19
20
|
#
|
20
21
|
# Example:
|
@@ -29,37 +30,52 @@ module ValidatesCaptcha
|
|
29
30
|
self.validate_captcha = false
|
30
31
|
result
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
# Returns +true+ if captcha validation is activated, otherwise +false+.
|
34
35
|
def validate_captcha? #:nodoc:
|
35
36
|
@validate_captcha == true
|
36
37
|
end
|
37
|
-
|
38
|
+
|
38
39
|
private
|
39
40
|
def validate_captcha=(value) #:nodoc:
|
40
41
|
@validate_captcha = value
|
41
42
|
end
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
module InstanceMethods #:nodoc:
|
45
46
|
def captcha_challenge #:nodoc:
|
46
47
|
return @captcha_challenge unless @captcha_challenge.blank?
|
47
48
|
@captcha_challenge = ValidatesCaptcha.provider.generate_challenge
|
48
49
|
end
|
49
|
-
|
50
|
-
|
50
|
+
|
51
|
+
def attributes_with_captcha_fields=(new_attributes, guard_protected_attributes = true)
|
52
|
+
if new_attributes && guard_protected_attributes &&
|
53
|
+
(new_attributes.key?('captcha_challenge') || new_attributes.key?(:captcha_challenge))
|
54
|
+
attributes = new_attributes.dup
|
55
|
+
attributes.stringify_keys!
|
56
|
+
|
57
|
+
self.captcha_challenge = attributes.delete('captcha_challenge')
|
58
|
+
self.captcha_solution = attributes.delete('captcha_solution')
|
59
|
+
|
60
|
+
new_attributes = attributes
|
61
|
+
end
|
62
|
+
|
63
|
+
send :attributes_without_captcha_fields=, new_attributes, guard_protected_attributes
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
51
67
|
def validate_captcha? #:nodoc:
|
52
68
|
self.class.validate_captcha?
|
53
69
|
end
|
54
|
-
|
70
|
+
|
55
71
|
def validate_captcha #:nodoc:
|
56
72
|
errors.add(:captcha_solution, :blank) and return if captcha_solution.blank?
|
57
73
|
errors.add(:captcha_solution, :invalid) unless captcha_valid?
|
58
74
|
end
|
59
|
-
|
75
|
+
|
60
76
|
def captcha_valid? #:nodoc:
|
61
77
|
ValidatesCaptcha.provider.solved?(captcha_challenge, captcha_solution)
|
62
|
-
end
|
78
|
+
end
|
63
79
|
end
|
64
80
|
end
|
65
81
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'action_view/helpers'
|
2
2
|
|
3
3
|
module ValidatesCaptcha
|
4
|
-
# Here is how you can implement your own captcha challenge provider. Create a
|
5
|
-
# class that conforms to the public method definitions of the example below
|
4
|
+
# Here is how you can implement your own captcha challenge provider. Create a
|
5
|
+
# class that conforms to the public method definitions of the example below
|
6
6
|
# and assign an instance of it to ValidatesCaptcha#provider=.
|
7
7
|
#
|
8
8
|
# Example:
|
@@ -26,7 +26,7 @@ module ValidatesCaptcha
|
|
26
26
|
#
|
27
27
|
# def render_challenge(sanitized_object_name, object, options = {})
|
28
28
|
# options[:id] = "#{sanitized_object_name}_captcha_question"
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# content_tag :span, "What's the reverse of '#{object.captcha_challenge}'?", options
|
31
31
|
# end
|
32
32
|
#
|
@@ -36,7 +36,7 @@ module ValidatesCaptcha
|
|
36
36
|
# "$('#{sanitized_object_name}_captcha_question').update(result.question); " \\
|
37
37
|
# "$('#{sanitized_object_name}_captcha_challenge').value = result.challenge; " \\
|
38
38
|
# "$('#{sanitized_object_name}_captcha_solution').value = '';"
|
39
|
-
#
|
39
|
+
#
|
40
40
|
# link_to_remote text, options.reverse_merge(:url => '/captchas/regenerate', :method => :get, :success => success), html_options
|
41
41
|
# end
|
42
42
|
#
|
@@ -44,7 +44,7 @@ module ValidatesCaptcha
|
|
44
44
|
# if env['PATH_INFO'] == '/captchas/regenerate'
|
45
45
|
# challenge = generate_challenge
|
46
46
|
# json = { :question => "What's the reverse of '#{challenge}'?", :challenge => challenge }.to_json
|
47
|
-
#
|
47
|
+
#
|
48
48
|
# [200, { 'Content-Type' => 'application/json' }, [json]]
|
49
49
|
# else
|
50
50
|
# [404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
@@ -69,61 +69,61 @@ module ValidatesCaptcha
|
|
69
69
|
# implementation instead of the default one, consult the documentation
|
70
70
|
# for the specific default class.
|
71
71
|
#
|
72
|
-
# The default captcha image generator uses ImageMagick's +convert+ command to
|
73
|
-
# create the captcha. So a recent and properly configured version of ImageMagick
|
74
|
-
# must be installed on the system. The version used while developing was 6.4.5.
|
75
|
-
# But you are not bound to ImageMagick. If you want to provide a custom image
|
76
|
-
# generator, take a look at the documentation for
|
72
|
+
# The default captcha image generator uses ImageMagick's +convert+ command to
|
73
|
+
# create the captcha. So a recent and properly configured version of ImageMagick
|
74
|
+
# must be installed on the system. The version used while developing was 6.4.5.
|
75
|
+
# But you are not bound to ImageMagick. If you want to provide a custom image
|
76
|
+
# generator, take a look at the documentation for
|
77
77
|
# ValidatesCaptcha::ImageGenerator::Simple on how to create your own.
|
78
78
|
class DynamicImage
|
79
79
|
include ActionView::Helpers
|
80
|
-
|
80
|
+
|
81
81
|
@@string_generator = nil
|
82
82
|
@@symmetric_encryptor = nil
|
83
83
|
@@image_generator = nil
|
84
|
-
|
84
|
+
|
85
85
|
class << self
|
86
86
|
# Returns the current captcha string generator. Defaults to an
|
87
87
|
# instance of the ValidatesCaptcha::StringGenerator::Simple class.
|
88
88
|
def string_generator
|
89
89
|
@@string_generator ||= ValidatesCaptcha::StringGenerator::Simple.new
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
# Sets the current captcha string generator. Used to set a
|
93
93
|
# custom string generator.
|
94
94
|
def string_generator=(generator)
|
95
95
|
@@string_generator = generator
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
# Returns the current captcha symmetric encryptor. Defaults to an
|
99
99
|
# instance of the ValidatesCaptcha::SymmetricEncryptor::Simple class.
|
100
100
|
def symmetric_encryptor
|
101
101
|
@@symmetric_encryptor ||= ValidatesCaptcha::SymmetricEncryptor::Simple.new
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
# Sets the current captcha symmetric encryptor. Used to set a
|
105
105
|
# custom symmetric encryptor.
|
106
106
|
def symmetric_encryptor=(encryptor)
|
107
107
|
@@symmetric_encryptor = encryptor
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
# Returns the current captcha image generator. Defaults to an
|
111
111
|
# instance of the ValidatesCaptcha::ImageGenerator::Simple class.
|
112
112
|
def image_generator
|
113
113
|
@@image_generator ||= ValidatesCaptcha::ImageGenerator::Simple.new
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
# Sets the current captcha image generator. Used to set a custom
|
117
117
|
# image generator.
|
118
118
|
def image_generator=(generator)
|
119
119
|
@@image_generator = generator
|
120
120
|
end
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
# This method is the one called by Rack.
|
124
|
-
#
|
125
|
-
# It returns HTTP status 404 if the path is not recognized. If the path is
|
126
|
-
# recognized, it returns HTTP status 200 and delivers the image if it could
|
124
|
+
#
|
125
|
+
# It returns HTTP status 404 if the path is not recognized. If the path is
|
126
|
+
# recognized, it returns HTTP status 200 and delivers the image if it could
|
127
127
|
# successfully decrypt the captcha code, otherwise HTTP status 422.
|
128
128
|
#
|
129
129
|
# Please take a look at the source code if you want to learn more.
|
@@ -132,16 +132,16 @@ module ValidatesCaptcha
|
|
132
132
|
if $1 == 'regenerate'
|
133
133
|
captcha_challenge = generate_challenge
|
134
134
|
json = { :captcha_challenge => captcha_challenge, :captcha_image_path => image_path(captcha_challenge) }.to_json
|
135
|
-
|
135
|
+
|
136
136
|
[200, { 'Content-Type' => 'application/json' }, [json]]
|
137
137
|
else
|
138
138
|
decrypted_code = decrypt($1)
|
139
|
-
|
139
|
+
|
140
140
|
if decrypted_code.nil?
|
141
141
|
[422, { 'Content-Type' => 'text/html' }, ['Unprocessable Entity']]
|
142
142
|
else
|
143
143
|
image_data = generate_image(decrypted_code)
|
144
|
-
|
144
|
+
|
145
145
|
response_headers = {
|
146
146
|
'Content-Length' => image_data.bytesize.to_s,
|
147
147
|
'Content-Type' => image_mime_type,
|
@@ -149,7 +149,7 @@ module ValidatesCaptcha
|
|
149
149
|
'Content-Transfer-Encoding' => 'binary',
|
150
150
|
'Cache-Control' => 'private'
|
151
151
|
}
|
152
|
-
|
152
|
+
|
153
153
|
[200, response_headers, [image_data]]
|
154
154
|
end
|
155
155
|
end
|
@@ -157,79 +157,79 @@ module ValidatesCaptcha
|
|
157
157
|
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
158
158
|
end
|
159
159
|
end
|
160
|
-
|
160
|
+
|
161
161
|
# Returns a captcha challenge.
|
162
162
|
def generate_challenge
|
163
163
|
encrypt(generate_code)
|
164
164
|
end
|
165
|
-
|
166
|
-
# Returns true if the captcha was solved using the given +challenge+ and +solution+,
|
165
|
+
|
166
|
+
# Returns true if the captcha was solved using the given +challenge+ and +solution+,
|
167
167
|
# otherwise false.
|
168
168
|
def solved?(challenge, solution)
|
169
169
|
decrypt(challenge) == solution
|
170
170
|
end
|
171
|
-
|
171
|
+
|
172
172
|
# Returns an image tag with the source set to the url of the captcha image.
|
173
|
-
#
|
173
|
+
#
|
174
174
|
# Internally calls Rails' +image_tag+ helper method, passing the +options+ argument.
|
175
175
|
def render_challenge(sanitized_object_name, object, options = {})
|
176
176
|
src = image_path(object.captcha_challenge)
|
177
|
-
|
177
|
+
|
178
178
|
options[:alt] ||= 'CAPTCHA'
|
179
179
|
options[:id] = "#{sanitized_object_name}_captcha_image"
|
180
|
-
|
180
|
+
|
181
181
|
image_tag src, options
|
182
182
|
end
|
183
|
-
|
184
|
-
# Returns an anchor tag that makes an AJAX request to fetch a new captcha code and updates
|
183
|
+
|
184
|
+
# Returns an anchor tag that makes an AJAX request to fetch a new captcha code and updates
|
185
185
|
# the captcha image after the request is complete.
|
186
|
-
#
|
187
|
-
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
188
|
-
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
186
|
+
#
|
187
|
+
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
188
|
+
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
189
189
|
# to be available on the web page.
|
190
190
|
#
|
191
|
-
# The anchor text defaults to 'Regenerate Captcha'. You can set this to a custom value
|
191
|
+
# The anchor text defaults to 'Regenerate Captcha'. You can set this to a custom value
|
192
192
|
# providing a +:text+ key in the +options+ hash.
|
193
193
|
def render_regenerate_challenge_link(sanitized_object_name, object, options = {}, html_options = {})
|
194
194
|
text = options.delete(:text) || 'Regenerate Captcha'
|
195
195
|
success = "var result = request.responseJSON; $('#{sanitized_object_name}_captcha_image').src = result.captcha_image_path; $('#{sanitized_object_name}_captcha_challenge').value = result.captcha_challenge; $('#{sanitized_object_name}_captcha_solution').value = '';"
|
196
|
-
|
196
|
+
|
197
197
|
link_to_remote text, options.reverse_merge(:url => regenerate_path, :method => :get, :success => success), html_options
|
198
198
|
end
|
199
|
-
|
200
|
-
private
|
199
|
+
|
200
|
+
private
|
201
201
|
def generate_image(code) #:nodoc:
|
202
202
|
self.class.image_generator.generate code
|
203
203
|
end
|
204
|
-
|
204
|
+
|
205
205
|
def image_mime_type #:nodoc:
|
206
206
|
self.class.image_generator.mime_type
|
207
207
|
end
|
208
|
-
|
208
|
+
|
209
209
|
def image_file_extension #:nodoc:
|
210
210
|
self.class.image_generator.file_extension
|
211
211
|
end
|
212
|
-
|
212
|
+
|
213
213
|
def encrypt(code) #:nodoc:
|
214
214
|
self.class.symmetric_encryptor.encrypt code
|
215
215
|
end
|
216
|
-
|
216
|
+
|
217
217
|
def decrypt(encrypted_code) #:nodoc:
|
218
218
|
self.class.symmetric_encryptor.decrypt encrypted_code
|
219
219
|
end
|
220
|
-
|
220
|
+
|
221
221
|
def generate_code #:nodoc:
|
222
222
|
self.class.string_generator.generate
|
223
223
|
end
|
224
|
-
|
224
|
+
|
225
225
|
def image_path(encrypted_code) #:nodoc:
|
226
226
|
"/captchas/#{encrypted_code}#{image_file_extension}"
|
227
227
|
end
|
228
|
-
|
228
|
+
|
229
229
|
def regenerate_path #:nodoc:
|
230
230
|
'/captchas/regenerate'
|
231
231
|
end
|
232
|
-
|
232
|
+
|
233
233
|
# This is needed by +link_to_remote+ called in +render_regenerate_link+.
|
234
234
|
def protect_against_forgery? #:nodoc:
|
235
235
|
false
|
@@ -237,4 +237,4 @@ module ValidatesCaptcha
|
|
237
237
|
end
|
238
238
|
end
|
239
239
|
end
|
240
|
-
|
240
|
+
|