panmind-recaptcha 1.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.
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
+