negative_captcha 0.2.beta

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Erik Peterson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,150 @@
1
+ h1. Negative Captcha
2
+
3
+ h2. What is a Negative Captcha?
4
+
5
+ A negative captcha has the exact same purpose as your run-of-the-mill image captcha: To keep bots from submitting forms. Image ("positive") captchas do this by implementing a step which only humans can do, but bots cannot: read jumbled characters from an image. But this is bad. It creates usability problems, it hurts conversion rates, and it confuses the shit out of lots of people. Why not do it the other way around? Negative captchas create a form that has tasks that only bots can perform, but humans cannot. This has the exact same effect, with (anecdotally) a much lower false positive identification rate when compared with positive captchas. All of this comes without making humans go through any extra trouble to submit the form. It really is win-win.
6
+
7
+ h2. How does it work?
8
+
9
+ In a negative captcha form there are two main parts and three ancillary parts. I'll explain them thusly.
10
+
11
+ h3. Honeypots
12
+
13
+ Honeypots are form fields which look exactly like real form fields. Bots will see them and fill them out. Humans cannot see them and thusly will not fill them out. They are hidden indirectly- usually by positioning them off to the left of the browser. They look kind of like this:
14
+
15
+ <pre>
16
+ <code>
17
+ <div style="position: absolute; left: -2000px;"><input type="text" name="name" value="" /></div>
18
+ </code>
19
+ </pre>
20
+
21
+ h3. Real fields
22
+
23
+ These fields are the ones humans will see, will subsequently fill out, and that you'll pull your real form data out of. The form name will be hashed so that bots will not know what it is. They look kind of like this:
24
+
25
+ <pre>
26
+ <code>
27
+ <input type="text" name="685966bd3a1975667b4777cc56188c7e" />
28
+ </code>
29
+ </pre>
30
+
31
+ h4. Timestamp
32
+
33
+ This is a field that is used in the hash key to make the hash different on every GET request, and to prevent replayability.
34
+
35
+ h4. Spinner
36
+
37
+ This is a rotating key that is used in the hash method to prevent replayability. I'm not sold on its usefulness.
38
+
39
+ h4. Secret key
40
+
41
+ This is simply some key that is used in the hashing method to prevent bots from backing out the name of the field from the hashed field name.
42
+
43
+ h2. How does Negative Captcha work?
44
+
45
+ h3. Installation
46
+
47
+ h4. Rails 3
48
+
49
+ You can let bundler install Negative Captcha by adding this line to your application’s Gemfile:
50
+
51
+ <pre>
52
+ <code>
53
+ gem 'negative-captcha', :git => 'git://github.com/stefants/negative-captcha.git'
54
+ </code>
55
+ </pre>
56
+
57
+ And then execute:
58
+
59
+ <pre>
60
+ <code>
61
+ bundle install
62
+ </code>
63
+ </pre>
64
+
65
+ h3. Controller Hooks
66
+
67
+ Place this before filter at the top of the controller you are protecting:
68
+
69
+ <pre>
70
+ <code>
71
+ before_filter :setup_negative_captcha, :only => [:new, :create]
72
+ </code>
73
+ </pre>
74
+
75
+ In the same controller include the following private method:
76
+
77
+ <pre>
78
+ <code>
79
+ private
80
+ def setup_negative_captcha
81
+ @captcha = NegativeCaptcha.new(
82
+ :secret => NEGATIVE_CAPTCHA_SECRET, #A secret key entered in environment.rb. 'rake secret' will give you a good one.
83
+ :spinner => request.remote_ip,
84
+ :fields => [:name, :email, :body], #Whatever fields are in your form
85
+ :params => params)
86
+ end
87
+ </code>
88
+ </pre>
89
+
90
+ Modify your POST action(s) to check for the validity of the negative captcha form
91
+
92
+ <pre>
93
+ <code>
94
+ def create
95
+ @comment = Comment.new(@captcha.values) #Decrypted params
96
+ if @captcha.valid? && @comment.save
97
+ redirect_to @comment
98
+ else
99
+ flash[:notice] = @captcha.error if @captcha.error
100
+ render :action => 'new'
101
+ end
102
+ end
103
+ </code>
104
+ </pre>
105
+
106
+ h3. Form Example
107
+
108
+ Modify your form to include the honeypots and other fields. You can probably leave any select, radio, and check box fields alone. The text field/text area helpers should be sufficient.
109
+
110
+ <pre>
111
+ <code>
112
+ <% form_tag comments_path do -%>
113
+ <%= raw negative_captcha(@captcha) %>
114
+ <ul class="contact_us">
115
+ <li>
116
+ <%= negative_label_tag(@captcha, :name, 'Name:') %>
117
+ <%= negative_text_field_tag @captcha, :name %>
118
+ </li>
119
+ <li>
120
+ <%= negative_label_tag(@captcha, :email, 'Email:') %>
121
+ <%= negative_text_field_tag @captcha, :email %>
122
+ </li>
123
+ <li>
124
+ <%= negative_label_tag(@captcha, :body, 'Your Comment:') %>
125
+ <%= negative_text_area_tag @captcha, :body %>
126
+ </li>
127
+ <li>
128
+ <%= submit_tag %>
129
+ </li>
130
+ </ul>
131
+ <% end -%>
132
+ </code>
133
+ </pre>
134
+
135
+ h3. Test and enjoy!
136
+
137
+ h2. Possible Gotchas and other concerns
138
+
139
+ * It is still possible for someone to write a bot to exploit a single site by closely examining the DOM. This means that if you are Yahoo, Google or Facebook, negative captchas will not be a complete solution. But if you have a small application, negative captchas will likely be a very, very good solution for you. There are no easy work-arounds to this quite yet. Let me know if you have one.
140
+ * I'm not a genius. It is possible that a bot can figure out the hashed values and determine which forms are which. I don't know how, but I think they might be able to. I welcome people who have thought this out more thoroughly to criticize this method and help me find solutions. I like this idea a lot and want it to succeed.
141
+
142
+ h2. Credit
143
+
144
+ The idea of a negative captcha is not mine. It originates (I think) from Damien Katz of CouchDB. I (Erik Peterson) wrote the plugin. Calvin Yu wrote the original class which I refactored quite a bit and made into the plugin.
145
+
146
+ We did this while working on "Skribit":http://skribit.com/, an Atlanta startup (if you have a blog, please check us out at "http://skribit.com/":http://skribit.com/)
147
+
148
+ If you have any questions, concerns or have found any bugs, please email me at erik@skribit.com
149
+
150
+ If you'd like to help improve or refactor this plugin, please create a fork on Github and let me know about it. "http://github.com/subwindow/negative-captcha":http://github.com/subwindow/negative-captcha
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Tests negative-captcha.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generates documentation for negative-captcha.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'NegativeCaptcha'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README.textile')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
@@ -0,0 +1,66 @@
1
+ module ActionView
2
+ module Helpers
3
+ class FormBuilder
4
+ def negative_text_field(captcha, method, options = {})
5
+ html = @template.negative_text_field_tag(
6
+ captcha,
7
+ method,
8
+ options
9
+ ).html_safe
10
+
11
+ if @object.errors[method].present?
12
+ html = "<div class='fieldWithErrors'>#{html}</div>"
13
+ end
14
+
15
+ html.html_safe
16
+ end
17
+
18
+ def negative_text_area(captcha, method, options = {})
19
+ html = @template.negative_text_area_tag(
20
+ captcha,
21
+ method,
22
+ options
23
+ ).html_safe
24
+
25
+ if @object.errors[method].present?
26
+ html = "<div class='fieldWithErrors'>#{html}</div>"
27
+ end
28
+
29
+ html.html_safe
30
+ end
31
+
32
+ def negative_hidden_field(captcha, method, options = {})
33
+ @template.negative_hidden_field_tag(captcha, method, options).html_safe
34
+ end
35
+
36
+ def negative_password_field(captcha, method, options = {})
37
+ html = @template.negative_password_field_tag(
38
+ captcha,
39
+ method,
40
+ options
41
+ ).html_safe
42
+
43
+ if @object.errors[method].present?
44
+ html = "<div class='fieldWithErrors'>#{html}</div>"
45
+ end
46
+
47
+ html.html_safe
48
+ end
49
+
50
+ def negative_label(captcha, method, name, options = {})
51
+ html = @template.negative_label_tag(
52
+ captcha,
53
+ method,
54
+ name,
55
+ options
56
+ ).html_safe
57
+
58
+ if @object.errors[method].present?
59
+ html = "<div class='fieldWithErrors'>#{html}</div>"
60
+ end
61
+
62
+ html.html_safe
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ module NegativeCaptchaViewHelpers
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ end
6
+
7
+ def negative_captcha(captcha)
8
+ [
9
+ hidden_field_tag('timestamp', captcha.timestamp),
10
+ hidden_field_tag('spinner', captcha.spinner),
11
+ ].join
12
+ end
13
+
14
+ def negative_text_field_tag(negative_captcha, field, options={})
15
+ text_field_tag(
16
+ negative_captcha.fields[field],
17
+ negative_captcha.values[field],
18
+ options
19
+ ) +
20
+ content_tag('div', :style => 'position: absolute; left: -2000px;') do
21
+ text_field_tag(field, '', :tabindex => '999', :autocomplete => 'off')
22
+ end
23
+ end
24
+
25
+ def negative_text_area_tag(negative_captcha, field, options={})
26
+ text_area_tag(
27
+ negative_captcha.fields[field],
28
+ negative_captcha.values[field],
29
+ options
30
+ ) +
31
+ content_tag('div', :style => 'position: absolute; left: -2000px;') do
32
+ text_area_tag(field, '', :tabindex => '999', :autocomplete => 'off')
33
+ end
34
+ end
35
+
36
+ def negative_hidden_field_tag(negative_captcha, field, options={})
37
+ hidden_field_tag(
38
+ negative_captcha.fields[field],
39
+ negative_captcha.values[field],
40
+ options
41
+ ) +
42
+ "<div style='position: absolute; left: -2000px;'>" +
43
+ hidden_field_tag(field, '', :tabindex => '999') +
44
+ "</div>"
45
+ end
46
+
47
+ def negative_password_field_tag(negative_captcha, field, options={})
48
+ password_field_tag(
49
+ negative_captcha.fields[field],
50
+ negative_captcha.values[field],
51
+ options) +
52
+ "<div style='position: absolute; left: -2000px;'>" +
53
+ password_field_tag(field, '', :tabindex => '999') +
54
+ "</div>"
55
+ end
56
+
57
+ def negative_label_tag(negative_captcha, field, name, options={})
58
+ label_tag(negative_captcha.fields[field], name, options)
59
+ end
60
+
61
+ #TODO: Select, check_box, etc
62
+ end
@@ -0,0 +1,82 @@
1
+ require 'digest/md5'
2
+
3
+ class NegativeCaptcha
4
+ attr_accessor :fields,
5
+ :values,
6
+ :secret,
7
+ :spinner,
8
+ :message,
9
+ :timestamp,
10
+ :error
11
+
12
+ def initialize(opts)
13
+ self.secret = opts[:secret] ||
14
+ Digest::MD5.hexdigest("this_is_a_secret_key")
15
+
16
+ if opts.has_key?(:params)
17
+ self.timestamp = opts[:params][:timestamp] || Time.now.to_i
18
+ else
19
+ self.timestamp = Time.now.to_i
20
+ end
21
+
22
+ self.spinner = Digest::MD5.hexdigest(
23
+ ([timestamp, secret] + Array(opts[:spinner])).join('-')
24
+ )
25
+
26
+ self.message = opts[:message] || <<-MESSAGE
27
+ Please try again.
28
+ This usually happens because an automated script attempted to submit this form.
29
+ MESSAGE
30
+
31
+ self.fields = opts[:fields].inject({}) do |hash, field_name|
32
+ hash[field_name] = Digest::MD5.hexdigest(
33
+ [field_name, spinner, secret].join('-')
34
+ )
35
+
36
+ hash
37
+ end
38
+
39
+ self.values = {}
40
+ self.error = "No params provided"
41
+
42
+ if opts[:params] && (opts[:params][:spinner] || opts[:params][:timestamp])
43
+ process(opts[:params])
44
+ end
45
+ end
46
+
47
+ def [](name)
48
+ fields[name]
49
+ end
50
+
51
+ def valid?
52
+ error.nil? || error == "" || error.empty?
53
+ end
54
+
55
+ def process(params)
56
+ timestamp_age = (Time.now.to_i - params[:timestamp].to_i).abs
57
+
58
+ if params[:timestamp].nil? || timestamp_age > 86400
59
+ self.error = "Error: Invalid timestamp. #{message}"
60
+ elsif params[:spinner] != spinner
61
+ self.error = "Error: Invalid spinner. #{message}"
62
+ elsif fields.keys.detect {|name| params[name] && params[name].length > 0}
63
+ self.error = <<-ERROR
64
+ Error: Hidden form fields were submitted that should not have been. #{message}
65
+ ERROR
66
+
67
+ false
68
+ else
69
+ self.error = ""
70
+
71
+ fields.each do |name, encrypted_name|
72
+ self.values[name] = params[encrypted_name]
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ require 'negative_captcha/view_helpers'
80
+ ActiveRecord::Base.send :include, NegativeCaptchaViewHelpers
81
+
82
+ require "negative_captcha/form_builder"
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: negative_captcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.beta
5
+ prerelease: 4
6
+ platform: ruby
7
+ authors:
8
+ - Erik Peterson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ description: A library to make creating negative captchas less painful
31
+ email:
32
+ - erik@subwindow.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - LICENSE
38
+ - README.textile
39
+ - Rakefile
40
+ - lib/negative_captcha.rb
41
+ - lib/negative_captcha/form_builder.rb
42
+ - lib/negative_captcha/view_helpers.rb
43
+ homepage: http://github.com/subwindow/negative-captcha
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib/
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>'
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.1
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: A library to make creating negative captchas less painful
67
+ test_files: []