validates_captcha 0.9.0

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 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