karsthammer-validates_captcha 0.9.5a
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 +22 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +320 -0
- data/Rakefile +102 -0
- data/lib/validates_captcha.rb +76 -0
- data/lib/validates_captcha/controller_validation.rb +63 -0
- data/lib/validates_captcha/form_builder.rb +16 -0
- data/lib/validates_captcha/form_helper.rb +39 -0
- data/lib/validates_captcha/image_generator/simple.rb +94 -0
- data/lib/validates_captcha/model_validation.rb +65 -0
- data/lib/validates_captcha/provider/dynamic_image.rb +240 -0
- data/lib/validates_captcha/provider/question.rb +110 -0
- data/lib/validates_captcha/provider/static_image.rb +224 -0
- data/lib/validates_captcha/string_generator/simple.rb +81 -0
- data/lib/validates_captcha/symmetric_encryptor/simple.rb +52 -0
- data/lib/validates_captcha/test_case.rb +12 -0
- data/lib/validates_captcha/version.rb +9 -0
- data/rails/init.rb +29 -0
- data/tasks/static_image_tasks.rake +33 -0
- data/test/cases/controller_validation_test.rb +222 -0
- data/test/cases/image_generator/simple_test.rb +34 -0
- data/test/cases/model_validation_test.rb +258 -0
- data/test/cases/provider/dynamic_image_test.rb +103 -0
- data/test/cases/provider/question_test.rb +41 -0
- data/test/cases/provider/static_image_test.rb +148 -0
- data/test/cases/string_generator/simple_test.rb +115 -0
- data/test/cases/symmetric_encryptor/simple_test.rb +28 -0
- data/test/cases/validates_captcha_test.rb +28 -0
- data/test/test_helper.rb +26 -0
- metadata +120 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'action_view/helpers'
|
|
2
|
+
|
|
3
|
+
module ValidatesCaptcha
|
|
4
|
+
module Provider
|
|
5
|
+
# A question/answer captcha provider.
|
|
6
|
+
class Question
|
|
7
|
+
include ActionView::Helpers
|
|
8
|
+
|
|
9
|
+
DEFAULT_QUESTIONS_AND_ANSWERS = {
|
|
10
|
+
"What's the capital of France?" => "Paris",
|
|
11
|
+
"What's the capital of Germany?" => "Berlin",
|
|
12
|
+
"What's the opposite of good?" => ["bad", "evil"],
|
|
13
|
+
"What's the opposite of love?" => "hate",
|
|
14
|
+
"What's the sum of 2 and 3?" => ["5", "five"],
|
|
15
|
+
"What's the product of 3 and 4?" => ["12", "twelve"],
|
|
16
|
+
"Thumb, tooth or hand: which is part of the head?" => "tooth",
|
|
17
|
+
"Bread, ham or milk: which is something to drink?" => "milk",
|
|
18
|
+
"What day is today, if yesterday was Friday?" => "Saturday",
|
|
19
|
+
"What day is today, if tomorrow is Tuesday?" => "Monday",
|
|
20
|
+
"What is the 2nd letter of the the third word in this question?" => "h",
|
|
21
|
+
"What color is the sky on a sunny day?" => "blue" }.freeze
|
|
22
|
+
|
|
23
|
+
@@questions_and_answers = DEFAULT_QUESTIONS_AND_ANSWERS
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Returns the current captcha questions/answers hash. Defaults to
|
|
27
|
+
# DEFAULT_QUESTIONS_AND_ANSWERS.
|
|
28
|
+
def questions_and_answers
|
|
29
|
+
@@questions_and_answers
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Sets the current captcha questions/answers hash. Used to set a
|
|
33
|
+
# custom questions/answers hash.
|
|
34
|
+
def questions_and_answers=(qna)
|
|
35
|
+
@@questions_and_answers = qna
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# This method is the one called by Rack.
|
|
40
|
+
#
|
|
41
|
+
# It returns HTTP status 404 if the path is not recognized. If the path is
|
|
42
|
+
# recognized, it returns HTTP status 200 and delivers a new challenge in
|
|
43
|
+
# JSON format.
|
|
44
|
+
#
|
|
45
|
+
# Please take a look at the source code if you want to learn more.
|
|
46
|
+
def call(env)
|
|
47
|
+
if env['PATH_INFO'] == regenerate_path
|
|
48
|
+
captcha_challenge = generate_challenge
|
|
49
|
+
json = { :captcha_challenge => captcha_challenge }.to_json
|
|
50
|
+
|
|
51
|
+
[200, { 'Content-Type' => 'application/json' }, [json]]
|
|
52
|
+
else
|
|
53
|
+
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns a captcha question.
|
|
58
|
+
def generate_challenge
|
|
59
|
+
self.class.questions_and_answers.keys[rand(self.class.questions_and_answers.keys.size)]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns true if the captcha was solved using the given +question+ and +answer+,
|
|
63
|
+
# otherwise false.
|
|
64
|
+
def solved?(question, answer)
|
|
65
|
+
return false unless self.class.questions_and_answers.key?(question)
|
|
66
|
+
answers = Array.wrap(self.class.questions_and_answers[question]).map(&:downcase)
|
|
67
|
+
answers.include?(answer.downcase)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns a span tag with the inner HTML set to the captcha question.
|
|
71
|
+
#
|
|
72
|
+
# Internally calls Rails' +content_tag+ helper method, passing the +options+ argument.
|
|
73
|
+
def render_challenge(sanitized_object_name, object, options = {})
|
|
74
|
+
options[:id] = "#{sanitized_object_name}_captcha_question"
|
|
75
|
+
|
|
76
|
+
content_tag :span, object.captcha_challenge, options
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Returns an anchor tag that makes an AJAX request to fetch a new question and updates
|
|
80
|
+
# the captcha challenge after the request is complete.
|
|
81
|
+
#
|
|
82
|
+
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
|
83
|
+
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
|
84
|
+
# to be available on the web page.
|
|
85
|
+
#
|
|
86
|
+
# The anchor text defaults to 'New question'. You can set this to a custom value
|
|
87
|
+
# providing a +:text+ key in the +options+ hash.
|
|
88
|
+
def render_regenerate_challenge_link(sanitized_object_name, object, options = {}, html_options = {})
|
|
89
|
+
text = options.delete(:text) || 'New question'
|
|
90
|
+
success = "var result = request.responseJSON; $('#{sanitized_object_name}_captcha_question').update(result.captcha_challenge); $('#{sanitized_object_name}_captcha_challenge').value = result.captcha_challenge; $('#{sanitized_object_name}_captcha_solution').value = '';"
|
|
91
|
+
|
|
92
|
+
link_to_remote text, options.reverse_merge(:url => regenerate_path, :method => :get, :success => success), html_options
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
def regenerate_path #:nodoc:
|
|
97
|
+
'/captchas/regenerate'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# This is needed by +link_to_remote+ called in +render_regenerate_link+.
|
|
101
|
+
def protect_against_forgery? #:nodoc:
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def solve(challenge) #:nodoc:
|
|
106
|
+
Array.wrap(self.class.questions_and_answers[challenge]).first
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
require 'action_view/helpers'
|
|
3
|
+
|
|
4
|
+
module ValidatesCaptcha
|
|
5
|
+
module Provider
|
|
6
|
+
# An image captcha provider that relies on pre-created captcha images.
|
|
7
|
+
#
|
|
8
|
+
# There is a Rake tast for creating the captcha images:
|
|
9
|
+
#
|
|
10
|
+
# rake validates_captcha:create_static_images
|
|
11
|
+
#
|
|
12
|
+
# This will create 3 images in #filesystem_dir. To create a
|
|
13
|
+
# different number of images, provide a COUNT argument:
|
|
14
|
+
#
|
|
15
|
+
# rake validates_captcha:create_static_images COUNT=50
|
|
16
|
+
#
|
|
17
|
+
# This class contains the getters and setters for the backend classes:
|
|
18
|
+
# image generator and string generator. This allows you to replace them
|
|
19
|
+
# with your custom implementations. For more information on how to bring
|
|
20
|
+
# the image provider to use your own implementation instead of the default
|
|
21
|
+
# one, consult the documentation for the specific default class.
|
|
22
|
+
#
|
|
23
|
+
# The default captcha image generator uses ImageMagick's +convert+ command to
|
|
24
|
+
# create the captcha. So a recent and properly configured version of ImageMagick
|
|
25
|
+
# must be installed on the system. The version used while developing was 6.4.5.
|
|
26
|
+
# But you are not bound to ImageMagick. If you want to provide a custom image
|
|
27
|
+
# generator, take a look at the documentation for
|
|
28
|
+
# ValidatesCaptcha::ImageGenerator::Simple on how to create your own.
|
|
29
|
+
class StaticImage
|
|
30
|
+
include ActionView::Helpers
|
|
31
|
+
|
|
32
|
+
SALT = "3f(61&831_fa0712d4a?b58-eb4b8$a2%.36378f".freeze
|
|
33
|
+
|
|
34
|
+
@@string_generator = nil
|
|
35
|
+
@@image_generator = nil
|
|
36
|
+
@@filesystem_dir = nil
|
|
37
|
+
@@web_dir = nil
|
|
38
|
+
@@salt = nil
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# Returns the current captcha string generator. Defaults to an
|
|
42
|
+
# instance of the ValidatesCaptcha::StringGenerator::Simple class.
|
|
43
|
+
def string_generator
|
|
44
|
+
@@string_generator ||= ValidatesCaptcha::StringGenerator::Simple.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Sets the current captcha string generator. Used to set a
|
|
48
|
+
# custom string generator.
|
|
49
|
+
def string_generator=(generator)
|
|
50
|
+
@@string_generator = generator
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the current captcha image generator. Defaults to an
|
|
54
|
+
# instance of the ValidatesCaptcha::ImageGenerator::Simple class.
|
|
55
|
+
def image_generator
|
|
56
|
+
@@image_generator ||= ValidatesCaptcha::ImageGenerator::Simple.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sets the current captcha image generator. Used to set a custom
|
|
60
|
+
# image generator.
|
|
61
|
+
def image_generator=(generator)
|
|
62
|
+
@@image_generator = generator
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the current captcha image file system directory. Defaults to
|
|
66
|
+
# +RAILS_ROOT/public/images/captchas+.
|
|
67
|
+
def filesystem_dir
|
|
68
|
+
@@filesystem_dir ||= ::File.join(::Rails.public_path, 'images', 'captchas')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sets the current captcha image file system directory. Used to set a custom
|
|
72
|
+
# image directory.
|
|
73
|
+
def filesystem_dir=(dir)
|
|
74
|
+
@@filesystem_dir = dir
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the current captcha image web directory. Defaults to
|
|
78
|
+
# +/images/captchas+.
|
|
79
|
+
def web_dir
|
|
80
|
+
@@web_dir ||= '/images/captchas'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sets the current captcha image web directory. Used to set a custom
|
|
84
|
+
# image directory.
|
|
85
|
+
def web_dir=(dir)
|
|
86
|
+
@@web_dir = dir
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the current salt used for encryption.
|
|
90
|
+
def salt
|
|
91
|
+
@@salt ||= SALT
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Sets the current salt used for encryption. Used to set a custom
|
|
95
|
+
# salt.
|
|
96
|
+
def salt=(salt)
|
|
97
|
+
@@salt = salt
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Return the encryption of the +code+ using #salt.
|
|
101
|
+
def encrypt(code)
|
|
102
|
+
::Digest::SHA1.hexdigest "#{salt}--#{code}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Creates a captcha image in the #filesystem_dir and returns
|
|
106
|
+
# the path to it and the code displayed on the image.
|
|
107
|
+
def create_image
|
|
108
|
+
code = string_generator.generate
|
|
109
|
+
encrypted_code = encrypt(code)
|
|
110
|
+
|
|
111
|
+
image_filename = "#{encrypted_code}#{image_generator.file_extension}"
|
|
112
|
+
image_path = File.join(filesystem_dir, image_filename)
|
|
113
|
+
image_bytes = image_generator.generate(code)
|
|
114
|
+
|
|
115
|
+
File.open image_path, 'w' do |os|
|
|
116
|
+
os.write image_bytes
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
return image_path, code
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
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 a new challenge in
|
|
127
|
+
# JSON format.
|
|
128
|
+
#
|
|
129
|
+
# Please take a look at the source code if you want to learn more.
|
|
130
|
+
def call(env)
|
|
131
|
+
if env['PATH_INFO'] == regenerate_path
|
|
132
|
+
captcha_challenge = generate_challenge
|
|
133
|
+
json = { :captcha_challenge => captcha_challenge, :captcha_image_path => image_path(captcha_challenge) }.to_json
|
|
134
|
+
|
|
135
|
+
[200, { 'Content-Type' => 'application/json' }, [json]]
|
|
136
|
+
else
|
|
137
|
+
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns an array containing the paths to the available captcha images.
|
|
142
|
+
def images
|
|
143
|
+
@images ||= Dir[File.join(filesystem_dir, "*#{image_file_extension}")]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns an array containing the available challenges (encrypted captcha codes).
|
|
147
|
+
def challenges
|
|
148
|
+
@challenges ||= images.map { |path| File.basename(path, image_file_extension) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns a captcha challenge.
|
|
152
|
+
def generate_challenge
|
|
153
|
+
raise("no captcha images found in #{filesystem_dir}") if challenges.empty?
|
|
154
|
+
|
|
155
|
+
challenges[rand(challenges.size)]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Returns true if the captcha was solved using the given +challenge+ and +solution+,
|
|
159
|
+
# otherwise false.
|
|
160
|
+
def solved?(challenge, solution)
|
|
161
|
+
challenge == encrypt(solution)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Returns an image tag with the source set to the url of the captcha image.
|
|
165
|
+
#
|
|
166
|
+
# Internally calls Rails' +image_tag+ helper method, passing the +options+ argument.
|
|
167
|
+
def render_challenge(sanitized_object_name, object, options = {})
|
|
168
|
+
src = image_path(object.captcha_challenge)
|
|
169
|
+
|
|
170
|
+
options[:alt] ||= 'CAPTCHA'
|
|
171
|
+
options[:id] = "#{sanitized_object_name}_captcha_image"
|
|
172
|
+
|
|
173
|
+
image_tag src, options
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Returns an anchor tag that makes an AJAX request to fetch a new captcha code and updates
|
|
177
|
+
# the captcha image after the request is complete.
|
|
178
|
+
#
|
|
179
|
+
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
|
180
|
+
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
|
181
|
+
# to be available on the web page.
|
|
182
|
+
#
|
|
183
|
+
# The anchor text defaults to 'Regenerate Captcha'. You can set this to a custom value
|
|
184
|
+
# providing a +:text+ key in the +options+ hash.
|
|
185
|
+
def render_regenerate_challenge_link(sanitized_object_name, object, options = {}, html_options = {})
|
|
186
|
+
text = options.delete(:text) || 'Regenerate Captcha'
|
|
187
|
+
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 = '';"
|
|
188
|
+
|
|
189
|
+
link_to_remote text, options.reverse_merge(:url => regenerate_path, :method => :get, :success => success), html_options
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
def regenerate_path #:nodoc:
|
|
194
|
+
'/captchas/regenerate'
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def image_file_extension #:nodoc:
|
|
198
|
+
self.class.image_generator.file_extension
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def image_path(encrypted_code) #:nodoc:
|
|
202
|
+
File.join(web_dir, "#{encrypted_code}#{image_file_extension}")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def encrypt(code) #:nodoc:
|
|
206
|
+
self.class.encrypt code
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def filesystem_dir #:nodoc:
|
|
210
|
+
self.class.filesystem_dir
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def web_dir #:nodoc:
|
|
214
|
+
self.class.web_dir
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# This is needed by +link_to_remote+ called in +render_regenerate_link+.
|
|
218
|
+
def protect_against_forgery? #:nodoc:
|
|
219
|
+
false
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module ValidatesCaptcha
|
|
2
|
+
module StringGenerator
|
|
3
|
+
# This class is responsible for generating the codes that are displayed
|
|
4
|
+
# on the captcha images. It does so by randomly selecting a number of
|
|
5
|
+
# characters from a predefined alphabet constisting of visually distinguishable
|
|
6
|
+
# letters and digits.
|
|
7
|
+
#
|
|
8
|
+
# The number of characters and the alphabet used when generating strings can
|
|
9
|
+
# be customized. See the #alphabet= and #length= methods for details.
|
|
10
|
+
#
|
|
11
|
+
# You can implement your own string generator by creating a
|
|
12
|
+
# class that conforms to the method definitions of the example below and
|
|
13
|
+
# assign an instance of it to
|
|
14
|
+
# ValidatesCaptcha::Provider::DynamicImage#string_generator=.
|
|
15
|
+
#
|
|
16
|
+
# Example for a custom string generator:
|
|
17
|
+
#
|
|
18
|
+
# class DictionaryGenerator
|
|
19
|
+
# DICTIONARY = ['foo', 'bar', 'baz', ...]
|
|
20
|
+
#
|
|
21
|
+
# def generate
|
|
22
|
+
# return DICTIONARY[rand(DICTIONARY.size)]
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# ValidatesCaptcha::Provider::DynamicImage.string_generator = DictionaryGenerator.new
|
|
27
|
+
# ValidatesCaptcha.provider = ValidatesCaptcha::Provider::DynamicImage.new
|
|
28
|
+
#
|
|
29
|
+
# You can also assign it to ValidatesCaptcha::Provider::StaticImage#string_generator=.
|
|
30
|
+
#
|
|
31
|
+
class Simple
|
|
32
|
+
@@alphabet = 'abdefghjkmnqrtABDEFGHJKLMNQRT234678923467892346789'
|
|
33
|
+
@@length = 6
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# Returns a string holding the chars used when randomly generating the text that
|
|
37
|
+
# is displayed on a captcha image. Defaults to a string of visually distinguishable
|
|
38
|
+
# letters and digits.
|
|
39
|
+
def alphabet
|
|
40
|
+
@@alphabet
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Sets the string to use as alphabet when randomly generating the text displayed
|
|
44
|
+
# on a captcha image. To increase the probability of appearing in the image, some
|
|
45
|
+
# characters might appear more than once in the string.
|
|
46
|
+
#
|
|
47
|
+
# You can set this to a custom alphabet within a Rails initializer:
|
|
48
|
+
#
|
|
49
|
+
# ValidatesCaptcha::StringGenerator::Simple.alphabet = '01'
|
|
50
|
+
def alphabet=(alphabet)
|
|
51
|
+
alphabet = alphabet.to_s.gsub(/\s/, '')
|
|
52
|
+
raise('alphabet cannot be blank') if alphabet.blank?
|
|
53
|
+
|
|
54
|
+
@@alphabet = alphabet
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the length to use when generating captcha codes. Defaults to 6.
|
|
58
|
+
def length
|
|
59
|
+
@@length
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Sets the length to use when generating captcha codes.
|
|
63
|
+
#
|
|
64
|
+
# You can set this to a custom length within a Rails initializer:
|
|
65
|
+
#
|
|
66
|
+
# ValidatesCaptcha::StringGenerator::Simple.length = 8
|
|
67
|
+
def length=(length)
|
|
68
|
+
@@length = length.to_i
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Randomly generates a string to be used as the code displayed on captcha images.
|
|
73
|
+
def generate
|
|
74
|
+
alphabet_chars = self.class.alphabet.split(//)
|
|
75
|
+
code_chars = []
|
|
76
|
+
self.class.length.times { code_chars << alphabet_chars[rand(alphabet_chars.size)] }
|
|
77
|
+
code_chars.join
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'active_support'
|
|
2
|
+
|
|
3
|
+
module ValidatesCaptcha
|
|
4
|
+
module SymmetricEncryptor
|
|
5
|
+
# This class is responsible for encrypting and decrypting captcha codes.
|
|
6
|
+
# It internally uses ActiveSupport's MessageEncryptor to do the string
|
|
7
|
+
# encryption/decryption.
|
|
8
|
+
#
|
|
9
|
+
# You can implement your own symmetric encryptor by creating a class
|
|
10
|
+
# that conforms to the method definitions of the example below and
|
|
11
|
+
# assign an instance of it to
|
|
12
|
+
# ValidatesCaptcha::Provider::DynamicImage#symmetric_encryptor=.
|
|
13
|
+
#
|
|
14
|
+
# Example for a custom symmetric encryptor:
|
|
15
|
+
#
|
|
16
|
+
# class ReverseString # very insecure and easily cracked
|
|
17
|
+
# def encrypt(code)
|
|
18
|
+
# code.reverse
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def decrypt(encrypted_code)
|
|
22
|
+
# encrypted_code.reverse
|
|
23
|
+
# rescue SomeKindOfDecryptionError
|
|
24
|
+
# nil
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# ValidatesCaptcha::Provider::DynamicImage.symmetric_encryptor = ReverseString.new
|
|
29
|
+
# ValidatesCaptcha.provider = ValidatesCaptcha::Provider::DynamicImage.new
|
|
30
|
+
#
|
|
31
|
+
# Please note: The #decrypt method should return +nil+ if decryption fails.
|
|
32
|
+
class Simple
|
|
33
|
+
KEY = ::ActiveSupport::SecureRandom.hex(64).freeze
|
|
34
|
+
|
|
35
|
+
def initialize #:nodoc:
|
|
36
|
+
@symmetric_encryptor = ::ActiveSupport::MessageEncryptor.new(KEY)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Encrypts a cleartext string.
|
|
40
|
+
def encrypt(code)
|
|
41
|
+
@symmetric_encryptor.encrypt(code).gsub('+', '%2B').gsub('/', '%2F')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Decrypts an encrypted string.
|
|
45
|
+
def decrypt(encrypted_code)
|
|
46
|
+
@symmetric_encryptor.decrypt encrypted_code.gsub('%2F', '/').gsub('%2B', '+')
|
|
47
|
+
rescue ::ActiveSupport::MessageEncryptor::InvalidMessage
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|