validates_captcha 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ == 0.9.0 (September 26, 2009)
2
+
3
+ * Initial version
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 Martin Andert
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.rdoc ADDED
@@ -0,0 +1,243 @@
1
+ = Validates Captcha
2
+
3
+ An image captcha verification approach for Rails apps, directly integrated into
4
+ ActiveRecord's validation mechanism and providing helpers for ActionController
5
+ and ActionView.
6
+
7
+ RDoc documentation (including this README as start page) can be found at
8
+ http://m4n.github.com/validates_captcha
9
+
10
+
11
+
12
+ == Basic Usage
13
+
14
+ Validates Captcha extends ActiveRecord, ActionController and ActionView with
15
+ helper methods that make it a snap to integrate captcha verification in your
16
+ Rails application.
17
+
18
+ Step #1: Extend the form of your view with the necessary captcha code display
19
+ and input logic.
20
+
21
+ # app/views/comments/new.html.erb
22
+ <% form_for @comment do |f| %>
23
+ <%= f.error_messages %>
24
+
25
+ <!-- standard input fields: -->
26
+ <p>
27
+ <%= f.label :name %><br />
28
+ <%= f.text_field :name %>
29
+ </p>
30
+ <!-- ... -->
31
+
32
+ <!-- now something new: -->
33
+ <p>
34
+ <%= f.label :captcha %><br />
35
+ <%= f.captcha_image %>
36
+ <%= f.captcha_field %>
37
+ </p>
38
+
39
+ <p>
40
+ <%= f.submit 'Create' %>
41
+ </p>
42
+ <% end %>
43
+
44
+ Step #2: Tell the controller that you want to validate
45
+ captchas.
46
+
47
+ class CommentsController < ApplicationController
48
+ validates_captcha
49
+
50
+ def create
51
+ # scaffold comment creation code ...
52
+ end
53
+
54
+ # more actions here ...
55
+ end
56
+
57
+ This activates captcha validation in every action of the controller
58
+ whenever an instance of class +Comment+ is saved.
59
+
60
+ Step #3: There's no step three!
61
+
62
+ To summarize: Put the following in your view.
63
+
64
+ <%= f.captcha_image %>
65
+ <%= f.captcha_field %>
66
+
67
+ And what you see below in the corresponding controller.
68
+
69
+ validates_captcha
70
+
71
+ Done.
72
+
73
+ == Customization
74
+
75
+ Because the +validates_captcha+ controller method internally creates an
76
+ around filter, you can (de)activate captcha validation for specific actions.
77
+
78
+ validates_captcha :only => [:create, :update]
79
+ validates_captcha :except => :reset
80
+
81
+ The class for which captcha validation is activated is derived from
82
+ the name of the controller. So putting +validates_captcha+ in a
83
+ +UsersController+ validates instances of the +User+ class.
84
+
85
+ You can customize the validated class using the +validates_captcha_of+ method.
86
+
87
+ class ArticlesController < ApplicationController
88
+ validates_captcha_of Post
89
+ validates_captcha_of :blog_entries, :except => :persist
90
+ validates_captcha_of 'users', :only => :store
91
+ end
92
+
93
+ Two kinds of errors are added to the model if captcha validation fails:
94
+ +:blank+ if no captcha code is submitted and +:invalid+ if a captcha code
95
+ is submitted but does not match the code displayed on the captcha image.
96
+ You can localize the error messages for the captcha as you usually do
97
+ for the other attributes.
98
+
99
+ models:
100
+ comment:
101
+ attributes:
102
+ captcha:
103
+ blank: 'must not be empty'
104
+ invalid: 'does not match the code displayed on the image'
105
+
106
+ What if the captcha's text is unreadable? There's also a form helper
107
+ method for captcha regeneration available. You can call it like this.
108
+
109
+ <p>
110
+ Captcha code unreadable? <%= f.regenerate_captcha_link %>
111
+ </p>
112
+
113
+ This generates an anchor tag that, when clicked, generates a new
114
+ captcha and updates the image. It makes an AJAX request to fetch a
115
+ new captcha code and updates the captcha image after the request is complete.
116
+
117
+ +regenerate_captcha_link+ internally calls Rails' #link_to_remote helper
118
+ method. So it relies on the Prototype javascript framework to be available
119
+ on the page.
120
+
121
+ The anchor's text defaults to 'Regenerate Captcha'. You can set this to
122
+ a custom value by providing a +:text+ key in the options hash.
123
+
124
+ <%= f.regenerate_captcha_link :text => 'Another captcha, please' %>
125
+
126
+ By default, captchas have a length of 6 characters and the text displayed
127
+ on the captcha image is created by randomly selecting characters from a
128
+ predefined alphabet constisting of visually distinguishable letters and digits.
129
+
130
+ The number of characters and the alphabet used when generating strings can
131
+ be customized. Just put the following in a Rails initializer and adjust the
132
+ values to your needs.
133
+
134
+ ValidatesCaptcha::StringGenerator::Simple.alphabet = '01'
135
+ ValidatesCaptcha::StringGenerator::Simple.length = 8
136
+
137
+ Apart from controllers, you can activate captcha validation for a model
138
+ using the class level +with_captcha_validation+ method added to
139
+ ActiveRecord::Base.
140
+
141
+ Comment.with_captcha_validation do
142
+ @comment = Comment.new(...)
143
+ @comment.save
144
+ end
145
+
146
+ This activates captcha validation on entering the block and deactivates it
147
+ on leaving the block.
148
+
149
+ Two new attribute like methods are added to ActiveRecord: +captcha+ and
150
+ +encrypted_captcha+. Those are made +attr_accessible+. The latter is
151
+ initialized to a randomly generated and encrypted captcha code on
152
+ instantation.
153
+
154
+ For a record to be valid, the value assigned to +captcha=+ must match the
155
+ decryption of the return value of +encrypted_captcha+. Within a
156
+ +with_captcha_validation+ block, calling +valid?+ (as is done by +save+,
157
+ +update_attributes+, etc.) will also validate the value of +captcha+
158
+ against the +encrypted_captcha+. Outside +with_captcha_validation+, no
159
+ captcha validation is performed.
160
+
161
+
162
+
163
+ == Extensibility
164
+
165
+ Validates Captcha delegates tasks like string and image generation,
166
+ encryption/decryption of captcha codes, and responding to captcha requests
167
+ to dedicated backend classes.
168
+
169
+ Those classes can easily be replaced by your custom implementations. So
170
+ you can achieve stronger encryption, can use a word list as captcha text
171
+ generation source, or can replace the captcha image generator with one
172
+ that creates images that are harder to crack.
173
+
174
+ Please see the documentation of the following classes for further information.
175
+
176
+ * ValidatesCaptcha::StringGenerator::Simple
177
+ * ValidatesCaptcha::ReversibleEncrypter::Simple
178
+ * ValidatesCaptcha::ImageGenerator::Simple
179
+ * ValidatesCaptcha::Middleware::Simple
180
+
181
+
182
+
183
+ == Dependencies
184
+
185
+ Using a Rack middleware to speed up the request/response cycle when fetching
186
+ captcha images, Validates Captcha requires Rails version 2.3 or greater.
187
+
188
+ The default captcha image generator uses ImageMagick's +convert+ command to
189
+ create the captcha. So a recent and properly configured version of ImageMagick
190
+ must be installed on the system. The version used while developing was 6.4.5.
191
+ But you are not bound to ImageMagick. If you want to provide a custom image
192
+ generator, take a look at the documentation for
193
+ ValidatesCaptcha::ImageGenerator::Simple on how to create your own.
194
+
195
+
196
+
197
+ == Download
198
+
199
+ The latest version of Validates Captcha can be found at
200
+ http://github.com/m4n/validates_captcha
201
+
202
+ Documentation can be generated from its distribution directory with the
203
+ following command.
204
+
205
+ % [sudo] rake rdoc
206
+
207
+ Tests can be executed from its distribution directory with the
208
+ following command.
209
+
210
+ % [sudo] rake test
211
+
212
+
213
+
214
+ == Installation
215
+
216
+ You can install Validates Captcha as a Rails plugin with the following command.
217
+
218
+ % ./script/plugin install git://github.com/m4n/validates_captcha.git
219
+
220
+ Or you can install it as a Gem with
221
+
222
+ % [sudo] gem install m4n-validates_captcha --source http://gems.github.com
223
+
224
+ and then configure it in your +environment.rb+ file as shown below.
225
+
226
+ Rails::Initializer.run do |config|
227
+ # ...
228
+ config.gem 'm4n-validates_captcha', :lib => 'validates_captcha'
229
+ # ...
230
+ end
231
+
232
+
233
+
234
+ == License
235
+
236
+ Validates Captcha is released under the MIT license.
237
+
238
+
239
+
240
+ == Copyright
241
+
242
+ Copyright (c) 2009 Martin Andert
243
+
data/Rakefile ADDED
@@ -0,0 +1,105 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+
7
+ require File.join(File.dirname(__FILE__), 'lib', 'validates_captcha', 'version')
8
+
9
+
10
+
11
+ PKG_NAME = 'validates_captcha'
12
+ PKG_VERSION = ValidatesCaptcha::VERSION::STRING
13
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
14
+
15
+ RELEASE_NAME = "REL #{PKG_VERSION}"
16
+
17
+ RUBY_FORGE_PROJECT = "validatecaptcha"
18
+ RUBY_FORGE_USER = "m4n"
19
+
20
+ PKG_FILES = FileList['[A-Z]*', 'lib/**/*', 'test/**/*', 'rails/*'].exclude(/\bCVS\b|~$/)
21
+
22
+
23
+
24
+ begin
25
+ require 'hanna/rdoctask'
26
+ rescue LoadError
27
+ require 'rake/rdoctask'
28
+ end
29
+
30
+ desc 'Generate documentation'
31
+ Rake::RDocTask.new do |rdoc|
32
+ rdoc.rdoc_dir = 'doc'
33
+ rdoc.title = "Validates Captcha"
34
+ rdoc.main = "README.rdoc"
35
+
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.options << '--charset' << 'utf-8'
38
+
39
+ rdoc.rdoc_files.include 'README.rdoc'
40
+ rdoc.rdoc_files.include 'MIT-LICENSE'
41
+ rdoc.rdoc_files.include 'CHANGELOG.rdoc'
42
+ rdoc.rdoc_files.include 'lib/**/*.rb'
43
+ rdoc.rdoc_files.exclude 'lib/validates_captcha/test_case.rb'
44
+ rdoc.rdoc_files.exclude 'lib/validates_captcha/version.rb'
45
+ end
46
+
47
+ namespace :rdoc do
48
+ desc 'Show documentation in Firefox'
49
+ task :show do
50
+ sh 'firefox doc/index.html'
51
+ end
52
+ end
53
+
54
+
55
+
56
+ desc 'Run tests by default'
57
+ task :default => :test
58
+
59
+ Rake::TestTask.new do |t|
60
+ t.libs << 'test'
61
+ t.test_files = FileList['test/**/*_test.rb']
62
+ #t.verbose = true
63
+ #t.warning = true
64
+ end
65
+
66
+
67
+
68
+ spec = eval(File.read('validates_captcha.gemspec'))
69
+
70
+ Rake::GemPackageTask.new(spec) do |pkg|
71
+ pkg.gem_spec = spec
72
+ pkg.need_tar = true
73
+ pkg.need_zip = true
74
+ end
75
+
76
+
77
+
78
+ desc 'Publish the release files to RubyForge'
79
+ task :release => [:package] do
80
+ require 'rubyforge'
81
+ require 'rake/contrib/rubyforgepublisher'
82
+
83
+ packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
84
+
85
+ rubyforge = RubyForge.new(YAML.load(File.read(RubyForge::CONFIG_F)), YAML.load(File.read('/home/andert/.rubyforge/auto-config.yml')))
86
+ rubyforge.login
87
+ rubyforge.add_release(9020, 12371, "REL #{PKG_VERSION}", *packages)
88
+ end
89
+
90
+
91
+
92
+ desc 'Uninstall local gem'
93
+ task :uninstall do
94
+ system "sudo gem uninstall #{PKG_NAME}"
95
+ end
96
+
97
+ desc 'Install local gem'
98
+ task :install => [:uninstall, :gem] do
99
+ system "sudo gem install pkg/#{PKG_NAME}-#{PKG_VERSION}.gem --no-ri --no-rdoc"
100
+ end
101
+
102
+
103
+
104
+ Dir['tasks/**/*.rake'].each { |tasks_file| load tasks_file }
105
+
@@ -0,0 +1,63 @@
1
+ module ValidatesCaptcha
2
+ module ControllerValidation
3
+ def self.included(base) #:nodoc:
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ # This module extends ActionController::Base with methods for captcha
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
12
+ # as first argument and passing the conditions hash.
13
+ #
14
+ # Usage Example:
15
+ #
16
+ # class UsersController < ApplicationController
17
+ # # Whenever a User gets saved, validate the captcha.
18
+ # validates_captcha
19
+ #
20
+ # def create
21
+ # # ... user creation code ...
22
+ # end
23
+ #
24
+ # # ... more actions ...
25
+ # end
26
+ def validates_captcha(conditions = {})
27
+ validates_captcha_of controller_name, conditions
28
+ end
29
+
30
+ # Activates captcha validation for the specified model.
31
+ #
32
+ # The +model+ argument can be a Class, a string, or a symbol.
33
+ #
34
+ # This method internally creates an around filter, passing the
35
+ # +conditions+ argument to it. So you can (de)activate captcha
36
+ # validation for specific actions.
37
+ #
38
+ # Usage examples:
39
+ #
40
+ # class UsersController < ApplicationController
41
+ # validates_captcha_of User
42
+ # validates_captcha_of :users, :only => [:create, :update]
43
+ # validates_captcha_of 'user', :except => :persist
44
+ #
45
+ # # ... actions go here ...
46
+ # end
47
+ def validates_captcha_of(model, conditions = {})
48
+ model = model.is_a?(Class) ? model : model.to_s.classify.constantize
49
+ without_formats = Array.wrap(conditions.delete(:without)).map(&:to_sym)
50
+
51
+ around_filter(conditions) do |controller, action|
52
+ if without_formats.include?(controller.request.format.to_sym)
53
+ action.call
54
+ else
55
+ model.with_captcha_validation do
56
+ action.call
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ module ValidatesCaptcha
2
+ module FormBuilder #:nodoc:
3
+ def captcha_image(options = {}) #:nodoc:
4
+ @template.captcha_image @object_name, options.merge(:object => @object)
5
+ end
6
+
7
+ def captcha_field(options = {}) #:nodoc:
8
+ @template.captcha_field @object_name, options.merge(:object => @object)
9
+ end
10
+
11
+ def regenerate_captcha_link(options = {}, html_options = {}) #:nodoc:
12
+ @template.regenerate_captcha_link @object_name, options.merge(:object => @object), html_options
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,51 @@
1
+ module ValidatesCaptcha
2
+ module FormHelper
3
+ # Returns an img tag with the src attribute pointing to the captcha image url.
4
+ #
5
+ # Internally calls Rails' #image_tag helper method, passing the +options+
6
+ # argument.
7
+ def captcha_image(object_name, options = {})
8
+ object = options.delete(:object)
9
+ src = ValidatesCaptcha.captcha_image_path(object.encrypted_captcha)
10
+ sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
11
+
12
+ options[:alt] ||= 'CAPTCHA'
13
+ options[:id] = "#{sanitized_object_name}_captcha_image"
14
+
15
+ image_tag src, options
16
+ end
17
+
18
+ # Returns an input tag of the "text" type tailored for entering the captcha code.
19
+ #
20
+ # Internally calls Rails' #text_field helper method, passing the +object_name+ and
21
+ # +options+ arguments.
22
+ def captcha_field(object_name, options = {})
23
+ options.delete(:id)
24
+
25
+ hidden_field(object_name, :encrypted_captcha, options) + text_field(object_name, :captcha, options)
26
+ end
27
+
28
+ # Returns an anchor tag that makes an AJAX request to fetch a new captcha code and updates
29
+ # the captcha image after the request is complete.
30
+ #
31
+ # Internally calls Rails' #link_to_remote helper method, passing the +options+ and
32
+ # +html_options+ arguments. So it relies on the Prototype javascript framework
33
+ # to be available on the web page.
34
+ #
35
+ # The anchor text defaults to 'Regenerate Captcha'. You can set this to a custom value
36
+ # providing a +:text+ key in the +options+ hash.
37
+ def regenerate_captcha_link(object_name, options = {}, html_options = {})
38
+ options.symbolize_keys!
39
+
40
+ object = options.delete(:object)
41
+ text = options.delete(:text) || 'Regenerate Captcha'
42
+ sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
43
+
44
+ url = ValidatesCaptcha.regenerate_captcha_path
45
+ success = "var result = request.responseJSON; $('#{sanitized_object_name}_captcha_image').src = result.captcha_image_path; $('#{sanitized_object_name}_encrypted_captcha').value = result.encrypted_captcha_code;"
46
+
47
+ link_to_remote text, options.reverse_merge(:url => url, :method => :get, :success => success), html_options
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,87 @@
1
+ module ValidatesCaptcha
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
5
+ # ImageMagick must be installed on the system for it to work properly.
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
10
+ # path to it.
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
15
+ # positioned lines are rendered on the canvas.
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
19
+ # for your own implementations.
20
+ #
21
+ # You can implement your own (better) image generator by creating a
22
+ # class that conforms to the method definitions of the example below and
23
+ # assign an instance of it to ValidatesCaptcha#image_generator=.
24
+ #
25
+ # Example for a custom image generator:
26
+ #
27
+ # class AdvancedImageGenerator
28
+ # def generate(captcha_text)
29
+ # # ... do your magic here ...
30
+ #
31
+ # return string_containing_image_bytes
32
+ # end
33
+ #
34
+ # def image_mime_type
35
+ # 'image/png'
36
+ # end
37
+ #
38
+ # def image_file_extension
39
+ # '.png'
40
+ # end
41
+ # end
42
+ #
43
+ # ValidatesCaptcha.image_generator = AdvancedImageGenerator.new
44
+ #
45
+ class Simple
46
+ MIME_TYPE = 'image/gif'.freeze
47
+ FILE_EXTENSION = '.gif'.freeze
48
+
49
+ # Returns a string containing the image bytes of the captcha.
50
+ # As the only argument, the cleartext captcha text must be passed.
51
+ def generate(captcha_code)
52
+ image_width = captcha_code.length * 20 + 10
53
+
54
+ cmd = []
55
+ cmd << "convert -size #{image_width}x40 xc:grey84 -background grey84 -fill black "
56
+
57
+ captcha_code.split(//).each_with_index do |char, i|
58
+ cmd << " -pointsize #{rand(8) + 15} "
59
+ cmd << " -weight #{rand(2) == 0 ? '4' : '8'}00 "
60
+ cmd << " -draw 'text #{5 + 20 * i},#{rand(10) + 20} \"#{char}\"' "
61
+ end
62
+
63
+ cmd << " -rotate #{rand(2) == 0 ? '-' : ''}5 -fill grey40 "
64
+
65
+ captcha_code.size.times do
66
+ cmd << " -draw 'line #{rand(image_width)},0 #{rand(image_width)},60' "
67
+ end
68
+
69
+ cmd << " gif:-"
70
+
71
+ image_magick_command = cmd.join
72
+
73
+ `#{image_magick_command}`
74
+ end
75
+
76
+ # Returns the image mime type. This is always 'image/gif'.
77
+ def image_mime_type
78
+ MIME_TYPE
79
+ end
80
+
81
+ # Returns the image file extension. This is always '.gif'.
82
+ def image_file_extension
83
+ FILE_EXTENSION
84
+ end
85
+ end
86
+ end
87
+ end