panmind-recaptcha 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.md +144 -0
  2. data/Rakefile +49 -0
  3. data/lib/panmind/recaptcha.rb +136 -0
  4. data/rails/init.rb +7 -0
  5. metadata +84 -0
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ ReCaptcha for Rails - With AJAX Validation
2
+ ==========================================
3
+
4
+ Purpose
5
+ -------
6
+
7
+ This plugin implements view helpers to generate ReCaptcha code,
8
+ to interface with the HTTP API for captcha verification, a DSL
9
+ to generate a `before_filter` and the necessary hacky code to
10
+ implement AJAX captcha validation.
11
+
12
+ Implementation
13
+ --------------
14
+
15
+ For AJAX validation, a request to ReCaptcha HTTP server must be
16
+ made in order to verify the user input, but captchas can be
17
+ checked only once.
18
+
19
+ The plugin, thus, in case of a successful verification, saves
20
+ uses the Rails `flash` to temporarily save this status in the
21
+ session and then the before filter skips the verification via
22
+ the ReCaptcha HTTP service.
23
+
24
+ Installation
25
+ ------------
26
+
27
+ Via RubyGems:
28
+
29
+ gem install panmind-recaptcha
30
+
31
+ Or via Rails Plugin:
32
+
33
+ script/plugin install git://github.com/Panmind/recaptcha.git
34
+
35
+ Usage
36
+ -----
37
+
38
+ In your config/environment.rb:
39
+
40
+ Panmind::Recaptcha.set(
41
+ :private_key => 'your private key',
42
+ :public_key => 'your public key
43
+ )
44
+
45
+ In your controller, say, the `UsersController` for a signup action:
46
+
47
+ require_valid_captcha :only => :create
48
+
49
+ private
50
+ def invalid_captcha
51
+ @user = User.new params[:user]
52
+ @user.errors.add_to_base('Captcha failed')
53
+
54
+ render :new, :layout => 'login'
55
+ end
56
+
57
+ The `invalid_captcha` method is called by the plugin when captcha
58
+ verification fails, and *must* be overwritten or a `NotImplementedError`
59
+ exception will be thrown.
60
+
61
+ In your view:
62
+
63
+ <%= recaptcha :label => 'Are you human?', :theme => 'clean' %>
64
+
65
+ You can pass any `RecaptchaOptions` valid option, as stated by the
66
+ service documentation. The only nonstandard option `:label` is used
67
+ by the plugin to print a label before the captcha widget.
68
+
69
+
70
+ AJAX Validation
71
+ ---------------
72
+
73
+ To cache the results of a successful captcha verification, you need
74
+ simply to pass the `:ajax => true` option to the `require_valid_captcha`
75
+ controller method.
76
+
77
+ require_valid_captcha :only => :create, :ajax => true
78
+
79
+ When the form is validated via AJAX, the maybe successful result will
80
+ be saved in the `flash` (thus set in the session store); when the form is
81
+ then submitted via a plain HTTP request, verification will be skipped.
82
+
83
+ On Panmind we use our `jquery.ajax-validation` plugin, that you
84
+ can download from http://github.com/Panmind/jquery-ajax-nav and
85
+ the Javascript code located in the `js/signup-sample.js` file.
86
+
87
+ On the backend, an example checker that returns different HTTP
88
+ status code follows:
89
+
90
+ def signup_checker
91
+ # If no email was provided, return 400
92
+ if params[:email].blank?
93
+ render :nothing => true, :status => :bad_request and return
94
+ end
95
+
96
+ email = CGI.unescape(params[:email])
97
+
98
+ # more thorough checks on email should go here
99
+
100
+ # If an user with this email already exist, return 406
101
+ if User.exists?(['email = ?', email])
102
+ render :nothing => true, :status => :not_acceptable and return
103
+ end
104
+
105
+ unless valid_captcha?
106
+ invalid_captcha and return
107
+ end
108
+
109
+ save_solved_captcha # This method sets a flag in the flash
110
+ render :nothing => true, :status => :ok
111
+ end
112
+
113
+ Moreover, the client Javascript code should be informed via an
114
+ HTTP status when validation fails, thus the `invalid_captcha`
115
+ must contain a special `render` when the request comes from XHR:
116
+
117
+ def invalid_captcha
118
+ # If the captcha is not valid, return a 412 (precondition failed)
119
+ render :nothing => true, :status => 412 and return true if request.xhr?
120
+
121
+ # Same invalid_captcha code as above
122
+ end
123
+
124
+
125
+ The latest XHR specification from the w3c states that cookies set
126
+ by responses to requests sent via XHR are to be honored by the browser.
127
+
128
+ The code was tested with IE (6,7,8), Safari (4, 5), Firefox 3, Chrome 5
129
+ and Opera 10.
130
+
131
+
132
+ Security
133
+ --------
134
+
135
+ As long as you use a session store backed on the server or cryptographically
136
+ sign the cookies used by the session cookie store (as Rails does by default)
137
+ there is no way to bypass the captcha when AJAX validation is enabled.
138
+
139
+
140
+ Compatibility
141
+ -------------
142
+
143
+ Tested with Rails 2.3.8 with the `rails_xss` plugin installed,
144
+ running under Ruby 1.9.1-p378.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+
4
+ require 'lib/panmind/recaptcha'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = 'panmind-recaptcha'
10
+
11
+ gemspec.summary = 'ReCaptcha for Rails - With AJAX Validation'
12
+ gemspec.description = 'ReCaptcha implements view helpers to generate ReCaptcha code, ' \
13
+ 'with the noscript counterpart, required methods that interface ' \
14
+ 'with the HTTP API for captcha verification, a DSL to generate a ' \
15
+ 'before_filter and the code to implement AJAX captcha validation.'
16
+
17
+ gemspec.authors = ['Marcello Barnaba']
18
+ gemspec.email = 'vjt@openssl.it'
19
+ gemspec.homepage = 'http://github.com/Panmind/recaptcha'
20
+
21
+ gemspec.files = %w( README.md Rakefile rails/init.rb ) + Dir['lib/**/*']
22
+ gemspec.extra_rdoc_files = %w( README.md )
23
+ gemspec.has_rdoc = true
24
+
25
+ gemspec.version = Panmind::Recaptcha::Version
26
+ gemspec.date = '2010-11-17'
27
+
28
+ gemspec.require_path = 'lib'
29
+
30
+ gemspec.add_dependency('rails', '~> 2.3.8')
31
+ end
32
+ rescue LoadError
33
+ puts 'Jeweler not available. Install it with: gem install jeweler'
34
+ end
35
+
36
+ desc 'Generate the rdoc'
37
+ Rake::RDocTask.new do |rdoc|
38
+ rdoc.rdoc_files.add %w( README.md lib/**/*.rb )
39
+
40
+ rdoc.main = 'README.md'
41
+ rdoc.title = 'ReCaptcha for Rails - With AJAX Validation'
42
+ end
43
+
44
+ desc 'Will someone help write tests?'
45
+ task :default do
46
+ puts
47
+ puts 'Can you help in writing tests? Please do :-)'
48
+ puts
49
+ end
@@ -0,0 +1,136 @@
1
+ require 'timeout'
2
+
3
+ if defined?(Rails) && Rails.env.test?
4
+ begin
5
+ require 'mocha'
6
+ rescue LoadError
7
+ print "\n!!\n!! ReCaptcha: to use the test helpers you should gem install mocha\n!!\n\n"
8
+ end
9
+ end
10
+
11
+ module Panmind
12
+ module Recaptcha
13
+ Version = 1.0
14
+
15
+ class << self
16
+ attr_accessor :private_key, :public_key, :request_timeout
17
+
18
+ def set(options)
19
+ self.private_key, self.public_key =
20
+ options.values_at(:private_key, :public_key)
21
+
22
+ # Defaults
23
+ #
24
+ self.request_timeout = options[:timeout] || 5
25
+ end
26
+
27
+ def enabled?
28
+ Rails.env.production?
29
+ end
30
+ end # << self
31
+
32
+ class ConfigurationError < StandardError; end
33
+
34
+ module Controller
35
+ def self.included(base)
36
+ base.instance_eval do
37
+ def require_valid_captcha(options = {})
38
+ if options.delete(:ajax)
39
+ options.update(:unless => :captcha_already_solved?)
40
+ end
41
+
42
+ before_filter :validate_recaptcha, options
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+ def validate_recaptcha
49
+ invalid_captcha unless valid_captcha?
50
+ end
51
+
52
+ def captcha_already_solved?
53
+ flash[:skip_captcha_check]
54
+ end
55
+
56
+ def save_solved_captcha
57
+ flash[:skip_captcha_check] = true
58
+ end
59
+
60
+ private
61
+ def valid_captcha?
62
+ return true unless Recaptcha.enabled?
63
+
64
+ challenge, response = params.values_at(
65
+ :recaptcha_challenge_field, :recaptcha_response_field)
66
+
67
+ return false if challenge.blank? || response.blank?
68
+
69
+ req =
70
+ Timeout.timeout(Recaptcha.request_timeout) do
71
+ uri = URI.parse("http://api-verify.recaptcha.net/verify")
72
+ Net::HTTP.post_form(uri,
73
+ :privatekey => Recaptcha.private_key,
74
+ :remoteip => request.remote_ip,
75
+ :challenge => challenge,
76
+ :response => response
77
+ )
78
+ end
79
+
80
+ res = req.body.split("\n")
81
+ return res.first == 'true'
82
+
83
+ rescue Timeout::Error
84
+ # Let it go...
85
+ true
86
+ end
87
+
88
+ def invalid_captcha
89
+ raise NotImplementedError, 'You must implement invalid_captcha in your controller'
90
+ end
91
+ end # Controller
92
+
93
+ module Helpers
94
+ def recaptcha(options = {})
95
+ return unless Recaptcha.enabled?
96
+
97
+ if Recaptcha.private_key.blank? || Recaptcha.public_key.blank?
98
+ raise ConfigurationError, 'ReCaptcha keys are missing'
99
+ end
100
+
101
+ label_text = options.delete(:label) || 'Enter the following words'
102
+
103
+ noscript_options = {:width => 420, :height => 320}.merge(
104
+ options.delete(:noscript) || {})
105
+
106
+ recaptcha_options =
107
+ options.empty? ? '' :
108
+ javascript_tag(%[var RecaptchaOptions = #{options.to_json}])
109
+
110
+ label_tag('recaptcha_response_field', label_text) + recaptcha_options +
111
+ %[<script type="text/javascript"
112
+ src="https://api-secure.recaptcha.net/challenge?k=#{Recaptcha.public_key}">
113
+ </script>
114
+
115
+ <noscript>
116
+ <iframe src="https://api-secure.recaptcha.net/noscript?k=#{Recaptcha.public_key}"
117
+ height="#{noscript_options[:width]}" width="#{noscript_options[:height]}" frameborder="0"></iframe><br>
118
+ <input type="text" class="text" name="recaptcha_challenge_field" tabindex="#{options[:tabindex]}"/>
119
+ <input type="hidden" name="recaptcha_response_field" value="manual_challenge" />
120
+ </noscript>
121
+ ].html_safe
122
+ end
123
+ end # Helpers
124
+
125
+ module TestHelpers
126
+ def mock_valid_captcha
127
+ @controller.stubs(:valid_captcha?).returns(true)
128
+ end
129
+
130
+ def mock_invalid_captcha
131
+ @controller.stubs(:valid_captcha?).returns(false)
132
+ end
133
+ end # TestHelpers
134
+
135
+ end # Recaptcha
136
+ end # Panmind
data/rails/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'panmind/recaptcha'
2
+
3
+ module Panmind::Recaptcha
4
+ ActionView::Base.instance_eval { include Helpers }
5
+ ActionController::Base.instance_eval { include Controller }
6
+ ActionController::TestCase.instance_eval { include TestHelpers } if Rails.env.test?
7
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: panmind-recaptcha
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ version: "1.0"
10
+ platform: ruby
11
+ authors:
12
+ - Marcello Barnaba
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-17 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 19
29
+ segments:
30
+ - 2
31
+ - 3
32
+ - 8
33
+ version: 2.3.8
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: ReCaptcha implements view helpers to generate ReCaptcha code, with the noscript counterpart, required methods that interface with the HTTP API for captcha verification, a DSL to generate a before_filter and the code to implement AJAX captcha validation.
37
+ email: vjt@openssl.it
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.md
44
+ files:
45
+ - README.md
46
+ - Rakefile
47
+ - lib/panmind/recaptcha.rb
48
+ - rails/init.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/Panmind/recaptcha
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.7
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: ReCaptcha for Rails - With AJAX Validation
83
+ test_files: []
84
+