validates_captcha 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +5 -0
- data/README.rdoc +35 -9
- data/lib/validates_captcha/provider/image.rb +0 -4
- data/lib/validates_captcha/provider/static_image.rb +224 -0
- data/lib/validates_captcha/version.rb +1 -1
- data/lib/validates_captcha.rb +1 -0
- data/tasks/static_image_tasks.rake +33 -0
- data/test/cases/controller_validation_test.rb +45 -5
- data/test/cases/model_validation_test.rb +61 -6
- data/test/cases/provider/static_image_test.rb +148 -0
- metadata +7 -3
data/CHANGELOG.rdoc
CHANGED
data/README.rdoc
CHANGED
@@ -5,10 +5,12 @@ ActiveRecord's validation mechanism and providing helpers for ActionController
|
|
5
5
|
and ActionView.
|
6
6
|
|
7
7
|
RDoc documentation (including this README as start page) can be found at
|
8
|
-
http://m4n.github.com/validates_captcha
|
8
|
+
http://m4n.github.com/validates_captcha. Validates Captcha was first
|
9
|
+
announced {here}[http://m4n.github.com/2009/09/28/introducing-validates-captcha.html].
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
By default, question/answer captcha challenges are used, but you can also switch
|
12
|
+
to the built-in image captcha providers. If you stick to ValidatesCaptcha’s API,
|
13
|
+
you can even implement your own captcha challenge provider.
|
12
14
|
|
13
15
|
|
14
16
|
|
@@ -18,8 +20,8 @@ Validates Captcha extends ActiveRecord, ActionController and ActionView with
|
|
18
20
|
helper methods that make it a snap to integrate captcha verification in your
|
19
21
|
Rails application.
|
20
22
|
|
21
|
-
<b>Step #1:</b> Extend
|
22
|
-
|
23
|
+
<b>Step #1:</b> Extend your view’s form with the necessary captcha display and
|
24
|
+
input logic.
|
23
25
|
|
24
26
|
# app/views/comments/new.html.erb
|
25
27
|
<% form_for @comment do |f| %>
|
@@ -152,7 +154,7 @@ Outside +with_captcha_validation+, no captcha validation is performed.
|
|
152
154
|
|
153
155
|
|
154
156
|
|
155
|
-
== Question/answer
|
157
|
+
== Question/answer challenge captchas (default provider)
|
156
158
|
|
157
159
|
You can set the captcha provider to use question/answer challenges with
|
158
160
|
the code below. It is best to put this in a Rails initializer.
|
@@ -170,10 +172,11 @@ to do it.
|
|
170
172
|
|
171
173
|
|
172
174
|
|
173
|
-
==
|
175
|
+
== Dynamic image challenge captchas
|
174
176
|
|
175
|
-
You can set the captcha provider to use image challenges
|
176
|
-
the code below.
|
177
|
+
You can set the captcha provider to use dynamically created image challenges
|
178
|
+
with the code below. Dynamic means that the captcha image is created on invocation.
|
179
|
+
If you want to utilize this provider, it is best to put this in a Rails initializer.
|
177
180
|
|
178
181
|
ValidatesCaptcha.provider = ValidatesCaptcha::Provider::Image.new
|
179
182
|
|
@@ -190,6 +193,29 @@ values to your needs.
|
|
190
193
|
|
191
194
|
|
192
195
|
|
196
|
+
== Static image challenge captchas
|
197
|
+
|
198
|
+
You can set the captcha provider to use static image challenges with
|
199
|
+
the code below. Static means that there exist some pre-created images
|
200
|
+
in a folder accessible by the web application. If you want to utilize
|
201
|
+
this provider, it is best to put this in a Rails initializer.
|
202
|
+
|
203
|
+
ValidatesCaptcha.provider = ValidatesCaptcha::Provider::StaticImage.new
|
204
|
+
|
205
|
+
There is a Rake tast for creating the static captcha images:
|
206
|
+
|
207
|
+
rake validates_captcha:create_static_images
|
208
|
+
|
209
|
+
This will create 3 images for you. To create a different number of images,
|
210
|
+
provide a COUNT argument:
|
211
|
+
|
212
|
+
rake validates_captcha:create_static_images COUNT=50
|
213
|
+
|
214
|
+
If you want to customize the path the images get saved to (or other stuff),
|
215
|
+
please see the documentation for ValidatesCaptcha::Provider::StaticImage.
|
216
|
+
|
217
|
+
|
218
|
+
|
193
219
|
== Extensibility
|
194
220
|
|
195
221
|
Don't like the built-in challenges? It's easy to extend them or to implement
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
require 'active_support/secure_random'
|
3
1
|
require 'action_view/helpers'
|
4
2
|
|
5
3
|
module ValidatesCaptcha
|
@@ -80,8 +78,6 @@ module ValidatesCaptcha
|
|
80
78
|
class Image
|
81
79
|
include ActionView::Helpers
|
82
80
|
|
83
|
-
KEY = ::ActiveSupport::SecureRandom.hex(32).freeze
|
84
|
-
|
85
81
|
@@string_generator = nil
|
86
82
|
@@reversible_encrypter = nil
|
87
83
|
@@image_generator = nil
|
@@ -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
|
+
|
data/lib/validates_captcha.rb
CHANGED
@@ -38,6 +38,7 @@ module ValidatesCaptcha
|
|
38
38
|
module Provider
|
39
39
|
autoload :Question, 'validates_captcha/provider/question'
|
40
40
|
autoload :Image, 'validates_captcha/provider/image'
|
41
|
+
autoload :StaticImage, 'validates_captcha/provider/static_image'
|
41
42
|
end
|
42
43
|
|
43
44
|
module StringGenerator
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'validates_captcha'
|
2
|
+
|
3
|
+
namespace :validates_captcha do
|
4
|
+
desc "Create 3 static captcha images in RAILS_ROOT/public/images/captchas/, specify a different number with COUNT=n"
|
5
|
+
task :create_static_images => [:create_static_image_dir, :clear_static_image_dir] do
|
6
|
+
count = ENV['COUNT'] ? ENV['COUNT'].to_i : 3
|
7
|
+
|
8
|
+
count.times do
|
9
|
+
path, code = ValidatesCaptcha::Provider::StaticImage.create_image
|
10
|
+
puts "Created #{path} with code #{code}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
task :create_static_image_dir => :environment do
|
15
|
+
image_dir = ValidatesCaptcha::Provider::StaticImage.filesystem_dir
|
16
|
+
|
17
|
+
unless File.exist?(image_dir)
|
18
|
+
FileUtils.mkdir_p image_dir
|
19
|
+
puts "Created directory #{image_dir}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
task :clear_static_image_dir => :environment do
|
24
|
+
image_dir = ValidatesCaptcha::Provider::StaticImage.filesystem_dir
|
25
|
+
image_files = Dir[File.join(image_dir, '*')]
|
26
|
+
|
27
|
+
if image_files.any?
|
28
|
+
FileUtils.rm image_files
|
29
|
+
puts "Cleared directory #{image_dir}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -76,13 +76,21 @@ end
|
|
76
76
|
class ControllerValidationTest < ActionController::TestCase
|
77
77
|
tests WidgetsController
|
78
78
|
|
79
|
-
def
|
79
|
+
def with_dynamic_image_provider(&block)
|
80
80
|
old_provider = ValidatesCaptcha.provider
|
81
81
|
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::Image.new
|
82
82
|
yield provider
|
83
83
|
ValidatesCaptcha.provider = old_provider
|
84
84
|
end
|
85
85
|
|
86
|
+
def with_static_image_provider(&block)
|
87
|
+
old_provider = ValidatesCaptcha.provider
|
88
|
+
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::StaticImage.new
|
89
|
+
provider.instance_variable_set "@images", ["/path/to/#{provider.send(:encrypt, 'hello')}#{provider.send(:image_file_extension)}"]
|
90
|
+
yield provider
|
91
|
+
ValidatesCaptcha.provider = old_provider
|
92
|
+
end
|
93
|
+
|
86
94
|
def with_question_provider(&block)
|
87
95
|
old_provider = ValidatesCaptcha.provider
|
88
96
|
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::Question.new
|
@@ -109,7 +117,7 @@ class ControllerValidationTest < ActionController::TestCase
|
|
109
117
|
end
|
110
118
|
|
111
119
|
test "calling #save method of controller should not assign @invalid" do
|
112
|
-
|
120
|
+
with_dynamic_image_provider do |provider|
|
113
121
|
challenge = provider.generate_challenge
|
114
122
|
solution = provider.send(:decrypt, challenge)
|
115
123
|
|
@@ -117,6 +125,14 @@ class ControllerValidationTest < ActionController::TestCase
|
|
117
125
|
assert_nil assigns(:invalid)
|
118
126
|
end
|
119
127
|
|
128
|
+
with_static_image_provider do |provider|
|
129
|
+
challenge = provider.generate_challenge
|
130
|
+
solution = 'hello'
|
131
|
+
|
132
|
+
post :save, { 'widget' => { 'captcha_challenge' => challenge, 'captcha_solution' => solution } }
|
133
|
+
assert_nil assigns(:invalid)
|
134
|
+
end
|
135
|
+
|
120
136
|
with_question_provider do |provider|
|
121
137
|
challenge = provider.generate_challenge
|
122
138
|
solution = provider.send(:solve, challenge)
|
@@ -127,7 +143,7 @@ class ControllerValidationTest < ActionController::TestCase
|
|
127
143
|
end
|
128
144
|
|
129
145
|
test "calling #store method of controller should assign @invalid" do
|
130
|
-
|
146
|
+
with_dynamic_image_provider do |provider|
|
131
147
|
challenge = provider.generate_challenge
|
132
148
|
solution = provider.send(:decrypt, challenge).reverse
|
133
149
|
|
@@ -135,6 +151,14 @@ class ControllerValidationTest < ActionController::TestCase
|
|
135
151
|
assert_not_nil assigns(:invalid)
|
136
152
|
end
|
137
153
|
|
154
|
+
with_static_image_provider do |provider|
|
155
|
+
challenge = provider.generate_challenge
|
156
|
+
solution = '---'
|
157
|
+
|
158
|
+
post :store, { 'widget' => { 'captcha_challenge' => challenge, 'captcha_solution' => solution } }
|
159
|
+
assert_not_nil assigns(:invalid)
|
160
|
+
end
|
161
|
+
|
138
162
|
with_question_provider do |provider|
|
139
163
|
challenge = provider.generate_challenge
|
140
164
|
solution = '---'
|
@@ -145,7 +169,7 @@ class ControllerValidationTest < ActionController::TestCase
|
|
145
169
|
end
|
146
170
|
|
147
171
|
test "calling #persist method of controller should assign @invalid" do
|
148
|
-
|
172
|
+
with_dynamic_image_provider do |provider|
|
149
173
|
challenge = provider.generate_challenge
|
150
174
|
solution = provider.send(:decrypt, challenge).reverse
|
151
175
|
|
@@ -153,6 +177,14 @@ class ControllerValidationTest < ActionController::TestCase
|
|
153
177
|
assert_not_nil assigns(:invalid)
|
154
178
|
end
|
155
179
|
|
180
|
+
with_static_image_provider do |provider|
|
181
|
+
challenge = provider.generate_challenge
|
182
|
+
solution = '---'
|
183
|
+
|
184
|
+
post :persist, { 'widget' => { 'captcha_challenge' => challenge, 'captcha_solution' => solution } }
|
185
|
+
assert_not_nil assigns(:invalid)
|
186
|
+
end
|
187
|
+
|
156
188
|
with_question_provider do |provider|
|
157
189
|
challenge = provider.generate_challenge
|
158
190
|
solution = '---'
|
@@ -163,7 +195,7 @@ class ControllerValidationTest < ActionController::TestCase
|
|
163
195
|
end
|
164
196
|
|
165
197
|
test "calling #bingo method of controller should not assign @invalid" do
|
166
|
-
|
198
|
+
with_dynamic_image_provider do |provider|
|
167
199
|
challenge = provider.generate_challenge
|
168
200
|
solution = provider.send(:decrypt, challenge).reverse
|
169
201
|
|
@@ -171,6 +203,14 @@ class ControllerValidationTest < ActionController::TestCase
|
|
171
203
|
assert_nil assigns(:invalid)
|
172
204
|
end
|
173
205
|
|
206
|
+
with_static_image_provider do |provider|
|
207
|
+
challenge = provider.generate_challenge
|
208
|
+
solution = '---'
|
209
|
+
|
210
|
+
post :bingo, { 'widget' => { 'captcha_challenge' => challenge, 'captcha_solution' => solution } }
|
211
|
+
assert_nil assigns(:invalid)
|
212
|
+
end
|
213
|
+
|
174
214
|
with_question_provider do |provider|
|
175
215
|
challenge = provider.generate_challenge
|
176
216
|
solution = '---'
|
@@ -1,13 +1,21 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class ModelValidationTest < ValidatesCaptcha::TestCase
|
4
|
-
def
|
4
|
+
def with_dynamic_image_provider(&block)
|
5
5
|
old_provider = ValidatesCaptcha.provider
|
6
6
|
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::Image.new
|
7
7
|
yield provider
|
8
8
|
ValidatesCaptcha.provider = old_provider
|
9
9
|
end
|
10
10
|
|
11
|
+
def with_static_image_provider(&block)
|
12
|
+
old_provider = ValidatesCaptcha.provider
|
13
|
+
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::StaticImage.new
|
14
|
+
provider.instance_variable_set "@images", ["/path/to/#{provider.send(:encrypt, 'hello')}#{provider.send(:image_file_extension)}"]
|
15
|
+
yield provider
|
16
|
+
ValidatesCaptcha.provider = old_provider
|
17
|
+
end
|
18
|
+
|
11
19
|
def with_question_provider(&block)
|
12
20
|
old_provider = ValidatesCaptcha.provider
|
13
21
|
provider = ValidatesCaptcha.provider = ValidatesCaptcha::Provider::Question.new
|
@@ -76,13 +84,20 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
76
84
|
end
|
77
85
|
|
78
86
|
test "not within a #with_captcha_validation block, calling valid? should return true if an invalid captcha_solution is set" do
|
79
|
-
|
87
|
+
with_dynamic_image_provider do |provider|
|
80
88
|
widget = Widget.new
|
81
89
|
widget.captcha_solution = provider.send(:decrypt, widget.captcha_challenge).reverse
|
82
90
|
|
83
91
|
assert widget.valid?
|
84
92
|
end
|
85
93
|
|
94
|
+
with_static_image_provider do |provider|
|
95
|
+
widget = Widget.new
|
96
|
+
widget.captcha_solution = '---'
|
97
|
+
|
98
|
+
assert widget.valid?
|
99
|
+
end
|
100
|
+
|
86
101
|
with_question_provider do |provider|
|
87
102
|
widget = Widget.new
|
88
103
|
widget.captcha_solution = '---'
|
@@ -92,13 +107,20 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
92
107
|
end
|
93
108
|
|
94
109
|
test "not within a #with_captcha_validation block, calling valid? should return true if a valid captcha_solution is set" do
|
95
|
-
|
110
|
+
with_dynamic_image_provider do |provider|
|
96
111
|
widget = Widget.new
|
97
112
|
widget.captcha_solution = provider.send(:decrypt, widget.captcha_challenge)
|
98
113
|
|
99
114
|
assert widget.valid?
|
100
115
|
end
|
101
116
|
|
117
|
+
with_static_image_provider do |provider|
|
118
|
+
widget = Widget.new
|
119
|
+
widget.captcha_solution = 'hello'
|
120
|
+
|
121
|
+
assert widget.valid?
|
122
|
+
end
|
123
|
+
|
102
124
|
with_question_provider do |provider|
|
103
125
|
widget = Widget.new
|
104
126
|
widget.captcha_solution = provider.send(:solve, widget.captcha_challenge)
|
@@ -130,7 +152,7 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
130
152
|
end
|
131
153
|
|
132
154
|
test "within a #with_captcha_validation block, calling valid? should return false if an invalid captcha_solution is set" do
|
133
|
-
|
155
|
+
with_dynamic_image_provider do |provider|
|
134
156
|
Widget.with_captcha_validation do
|
135
157
|
widget = Widget.new
|
136
158
|
widget.captcha_solution = provider.send(:decrypt, widget.captcha_challenge).reverse
|
@@ -141,6 +163,17 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
141
163
|
end
|
142
164
|
end
|
143
165
|
|
166
|
+
with_static_image_provider do |provider|
|
167
|
+
Widget.with_captcha_validation do
|
168
|
+
widget = Widget.new
|
169
|
+
widget.captcha_solution = '---'
|
170
|
+
|
171
|
+
assert !widget.valid?
|
172
|
+
assert_equal 1, Array.wrap(widget.errors[:captcha_solution]).size
|
173
|
+
assert Array.wrap(widget.errors[:captcha_solution]).first.include?('invalid')
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
144
177
|
with_question_provider do |provider|
|
145
178
|
Widget.with_captcha_validation do
|
146
179
|
widget = Widget.new
|
@@ -154,7 +187,7 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
154
187
|
end
|
155
188
|
|
156
189
|
test "within a #with_captcha_validation block, calling valid? should return true if a valid captcha_solution is set" do
|
157
|
-
|
190
|
+
with_dynamic_image_provider do |provider|
|
158
191
|
Widget.with_captcha_validation do
|
159
192
|
widget = Widget.new
|
160
193
|
widget.captcha_solution = provider.send(:decrypt, widget.captcha_challenge)
|
@@ -163,6 +196,15 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
163
196
|
end
|
164
197
|
end
|
165
198
|
|
199
|
+
with_static_image_provider do |provider|
|
200
|
+
Widget.with_captcha_validation do
|
201
|
+
widget = Widget.new
|
202
|
+
widget.captcha_solution = 'hello'
|
203
|
+
|
204
|
+
assert widget.valid?
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
166
208
|
with_question_provider do |provider|
|
167
209
|
Widget.with_captcha_validation do
|
168
210
|
widget = Widget.new
|
@@ -174,7 +216,7 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
174
216
|
end
|
175
217
|
|
176
218
|
test "with #with_captcha_validation block, calling valid? before and after the block should return true if valid? returned false within block" do
|
177
|
-
|
219
|
+
with_dynamic_image_provider do |provider|
|
178
220
|
widget = Widget.new
|
179
221
|
widget.captcha_solution = provider.send(:decrypt, widget.captcha_challenge).reverse
|
180
222
|
|
@@ -187,6 +229,19 @@ class ModelValidationTest < ValidatesCaptcha::TestCase
|
|
187
229
|
assert widget.valid?
|
188
230
|
end
|
189
231
|
|
232
|
+
with_static_image_provider do |provider|
|
233
|
+
widget = Widget.new
|
234
|
+
widget.captcha_solution = '---'
|
235
|
+
|
236
|
+
assert widget.valid?
|
237
|
+
|
238
|
+
Widget.with_captcha_validation do
|
239
|
+
assert !widget.valid?
|
240
|
+
end
|
241
|
+
|
242
|
+
assert widget.valid?
|
243
|
+
end
|
244
|
+
|
190
245
|
with_question_provider do |provider|
|
191
246
|
widget = Widget.new
|
192
247
|
widget.captcha_solution = '---'
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
STATIC_IMAGE = ValidatesCaptcha::Provider::StaticImage
|
4
|
+
|
5
|
+
unless defined?(::Rails)
|
6
|
+
module Rails
|
7
|
+
def self.public_path
|
8
|
+
'public'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class StaticImageTest < ValidatesCaptcha::TestCase
|
14
|
+
test "defines a class level #string_generator method" do
|
15
|
+
assert_respond_to STATIC_IMAGE, :string_generator
|
16
|
+
end
|
17
|
+
|
18
|
+
test "defines a class level #string_generator= method" do
|
19
|
+
assert_respond_to STATIC_IMAGE, :string_generator=
|
20
|
+
end
|
21
|
+
|
22
|
+
test "#string_generator method's return value should equal the value set using the #string_generator= method" do
|
23
|
+
old_string_generator = STATIC_IMAGE.string_generator
|
24
|
+
|
25
|
+
STATIC_IMAGE.string_generator = 'abc'
|
26
|
+
assert_equal 'abc', STATIC_IMAGE.string_generator
|
27
|
+
|
28
|
+
STATIC_IMAGE.string_generator = old_string_generator
|
29
|
+
end
|
30
|
+
|
31
|
+
test "defines a class level #image_generator method" do
|
32
|
+
assert_respond_to STATIC_IMAGE, :image_generator
|
33
|
+
end
|
34
|
+
|
35
|
+
test "defines a class level #image_generator= method" do
|
36
|
+
assert_respond_to STATIC_IMAGE, :image_generator=
|
37
|
+
end
|
38
|
+
|
39
|
+
test "#image_generator method's return value should equal the value set using the #image_generator= method" do
|
40
|
+
old_image_generator = STATIC_IMAGE.image_generator
|
41
|
+
|
42
|
+
STATIC_IMAGE.image_generator = 'abc'
|
43
|
+
assert_equal 'abc', STATIC_IMAGE.image_generator
|
44
|
+
|
45
|
+
STATIC_IMAGE.image_generator = old_image_generator
|
46
|
+
end
|
47
|
+
|
48
|
+
test "defines a class level #salt method" do
|
49
|
+
assert_respond_to STATIC_IMAGE, :salt
|
50
|
+
end
|
51
|
+
|
52
|
+
test "defines a class level #salt= method" do
|
53
|
+
assert_respond_to STATIC_IMAGE, :salt=
|
54
|
+
end
|
55
|
+
|
56
|
+
test "#salt method's return value should equal the value set using the #salt= method" do
|
57
|
+
old_salt = STATIC_IMAGE.salt
|
58
|
+
|
59
|
+
STATIC_IMAGE.salt = 'abc'
|
60
|
+
assert_equal 'abc', STATIC_IMAGE.salt
|
61
|
+
|
62
|
+
STATIC_IMAGE.salt = old_salt
|
63
|
+
end
|
64
|
+
|
65
|
+
test "defines a class level #filesystem_dir method" do
|
66
|
+
assert_respond_to STATIC_IMAGE, :filesystem_dir
|
67
|
+
end
|
68
|
+
|
69
|
+
test "defines a class level #filesystem_dir= method" do
|
70
|
+
assert_respond_to STATIC_IMAGE, :filesystem_dir=
|
71
|
+
end
|
72
|
+
|
73
|
+
test "#filesystem_dir method's return value should equal the value set using the #filesystem_dir= method" do
|
74
|
+
old_filesystem_dir = STATIC_IMAGE.filesystem_dir
|
75
|
+
|
76
|
+
STATIC_IMAGE.filesystem_dir = 'abc'
|
77
|
+
assert_equal 'abc', STATIC_IMAGE.filesystem_dir
|
78
|
+
|
79
|
+
STATIC_IMAGE.filesystem_dir = old_filesystem_dir
|
80
|
+
end
|
81
|
+
|
82
|
+
test "defines a class level #web_dir method" do
|
83
|
+
assert_respond_to STATIC_IMAGE, :web_dir
|
84
|
+
end
|
85
|
+
|
86
|
+
test "defines a class level #web_dir= method" do
|
87
|
+
assert_respond_to STATIC_IMAGE, :web_dir=
|
88
|
+
end
|
89
|
+
|
90
|
+
test "#web_dir method's return value should equal the value set using the #web_dir= method" do
|
91
|
+
old_web_dir = STATIC_IMAGE.web_dir
|
92
|
+
|
93
|
+
STATIC_IMAGE.web_dir = 'abc'
|
94
|
+
assert_equal 'abc', STATIC_IMAGE.web_dir
|
95
|
+
|
96
|
+
STATIC_IMAGE.web_dir = old_web_dir
|
97
|
+
end
|
98
|
+
|
99
|
+
test "calling #call with unrecognized path should have response status 404" do
|
100
|
+
result = STATIC_IMAGE.new.call 'PATH_INFO' => '/unrecognized'
|
101
|
+
|
102
|
+
assert_equal 404, result.first
|
103
|
+
end
|
104
|
+
|
105
|
+
test "calling #call with regenerate path should have response status 200" do
|
106
|
+
si = STATIC_IMAGE.new
|
107
|
+
si.instance_variable_set "@challenges", ['abc']
|
108
|
+
|
109
|
+
result = si.call 'PATH_INFO' => si.send(:regenerate_path)
|
110
|
+
|
111
|
+
assert_equal 200, result.first
|
112
|
+
end
|
113
|
+
|
114
|
+
test "calling #call with regenerate path should have content type response header set to application/json" do
|
115
|
+
si = STATIC_IMAGE.new
|
116
|
+
si.instance_variable_set "@challenges", ['abc']
|
117
|
+
|
118
|
+
result = si.call 'PATH_INFO' => si.send(:regenerate_path)
|
119
|
+
|
120
|
+
assert result.second.key?('Content-Type')
|
121
|
+
assert_equal 'application/json', result.second['Content-Type']
|
122
|
+
end
|
123
|
+
|
124
|
+
test "calling #generate_challenge should raise runtime error if no images are available" do
|
125
|
+
si = STATIC_IMAGE.new
|
126
|
+
si.instance_variable_set "@images", []
|
127
|
+
|
128
|
+
assert_raises RuntimeError do
|
129
|
+
si.generate_challenge
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
test "calling #generate_challenge should not raise runtime error if an images is available" do
|
134
|
+
si = STATIC_IMAGE.new
|
135
|
+
si.instance_variable_set "@images", ['/path/to/an/image.gif']
|
136
|
+
|
137
|
+
assert_nothing_raised do
|
138
|
+
si.generate_challenge
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
test "calling #generate_challenge should return the image file basename without extension" do
|
143
|
+
si = STATIC_IMAGE.new
|
144
|
+
si.instance_variable_set "@images", ["/path/to/an/captcha-image#{si.send(:image_file_extension)}"]
|
145
|
+
|
146
|
+
assert_equal 'captcha-image', si.generate_challenge
|
147
|
+
end
|
148
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validates_captcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Andert
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-07 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -55,16 +55,19 @@ files:
|
|
55
55
|
- lib/validates_captcha/model_validation.rb
|
56
56
|
- lib/validates_captcha/provider/image.rb
|
57
57
|
- lib/validates_captcha/provider/question.rb
|
58
|
+
- lib/validates_captcha/provider/static_image.rb
|
58
59
|
- lib/validates_captcha/reversible_encrypter/simple.rb
|
59
60
|
- lib/validates_captcha/string_generator/simple.rb
|
60
61
|
- lib/validates_captcha/test_case.rb
|
61
62
|
- lib/validates_captcha/version.rb
|
62
63
|
- rails/init.rb
|
64
|
+
- tasks/static_image_tasks.rake
|
63
65
|
- test/cases/controller_validation_test.rb
|
64
66
|
- test/cases/image_generator/simple_test.rb
|
65
67
|
- test/cases/model_validation_test.rb
|
66
68
|
- test/cases/provider/image_test.rb
|
67
69
|
- test/cases/provider/question_test.rb
|
70
|
+
- test/cases/provider/static_image_test.rb
|
68
71
|
- test/cases/reversible_encrypter/simple_test.rb
|
69
72
|
- test/cases/string_generator/simple_test.rb
|
70
73
|
- test/cases/validates_captcha_test.rb
|
@@ -76,7 +79,7 @@ licenses: []
|
|
76
79
|
post_install_message:
|
77
80
|
rdoc_options:
|
78
81
|
- --title
|
79
|
-
- Validates Captcha 0.9.
|
82
|
+
- Validates Captcha 0.9.4
|
80
83
|
- --main
|
81
84
|
- README.rdoc
|
82
85
|
- --line-numbers
|
@@ -110,6 +113,7 @@ test_files:
|
|
110
113
|
- test/cases/model_validation_test.rb
|
111
114
|
- test/cases/provider/image_test.rb
|
112
115
|
- test/cases/provider/question_test.rb
|
116
|
+
- test/cases/provider/static_image_test.rb
|
113
117
|
- test/cases/reversible_encrypter/simple_test.rb
|
114
118
|
- test/cases/string_generator/simple_test.rb
|
115
119
|
- test/cases/validates_captcha_test.rb
|