validates_captcha 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|