invisible_captcha 0.6.5 → 0.7.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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +5 -0
  4. data/README.md +107 -47
  5. data/Rakefile +17 -1
  6. data/invisible_captcha.gemspec +2 -2
  7. data/lib/invisible_captcha.rb +28 -24
  8. data/lib/invisible_captcha/controller_ext.rb +48 -0
  9. data/lib/invisible_captcha/form_helpers.rb +3 -7
  10. data/lib/invisible_captcha/railtie.rb +9 -0
  11. data/lib/invisible_captcha/validator.rb +3 -7
  12. data/lib/invisible_captcha/version.rb +1 -1
  13. data/lib/invisible_captcha/view_helpers.rb +25 -23
  14. data/spec/controllers_spec.rb +25 -0
  15. data/spec/dummy/README.md +3 -0
  16. data/spec/dummy/Rakefile +7 -0
  17. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  18. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  20. data/spec/dummy/app/controllers/topics_controller.rb +27 -0
  21. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  22. data/spec/dummy/app/mailers/.gitkeep +0 -0
  23. data/spec/dummy/app/models/.gitkeep +0 -0
  24. data/spec/dummy/app/models/topic.rb +18 -0
  25. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/spec/dummy/app/views/topics/new.html.erb +17 -0
  27. data/spec/dummy/config.ru +4 -0
  28. data/spec/dummy/config/application.rb +20 -0
  29. data/spec/dummy/config/boot.rb +5 -0
  30. data/spec/dummy/config/environment.rb +5 -0
  31. data/spec/dummy/config/environments/development.rb +30 -0
  32. data/spec/dummy/config/environments/production.rb +67 -0
  33. data/spec/dummy/config/environments/test.rb +34 -0
  34. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/spec/dummy/config/initializers/inflections.rb +15 -0
  36. data/spec/dummy/config/initializers/invisible_captcha.rb +6 -0
  37. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  38. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  39. data/spec/dummy/config/initializers/session_store.rb +8 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  41. data/spec/dummy/config/locales/en.yml +5 -0
  42. data/spec/dummy/config/routes.rb +5 -0
  43. data/spec/dummy/lib/assets/.gitkeep +0 -0
  44. data/spec/dummy/log/.gitkeep +0 -0
  45. data/spec/dummy/public/404.html +26 -0
  46. data/spec/dummy/public/422.html +26 -0
  47. data/spec/dummy/public/500.html +25 -0
  48. data/spec/dummy/public/favicon.ico +0 -0
  49. data/spec/dummy/script/rails +5 -0
  50. data/spec/helpers_spec.rb +43 -0
  51. data/spec/invisible_captcha_spec.rb +19 -0
  52. data/spec/spec_helper.rb +16 -0
  53. data/spec/validator_spec.rb +12 -0
  54. metadata +100 -24
  55. data/lib/invisible_captcha/controller_methods.rb +0 -21
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 57c21321c483942e15254c832843c09cbbba7830
4
+ data.tar.gz: 6c18c874fdd903093e1695a362064fd2d2b274cc
5
+ SHA512:
6
+ metadata.gz: ed8400648117aac3086d26176f2f9d67eb615f737f4078ed5c817bb3604ac8d85b295266c92bcca9ceeba25953ee68601a69a79044b804b3287bfd8c28b1eaf0
7
+ data.tar.gz: be584800363bf741d078f3a09f931e98544da1af91ce7757b78b2d5e4e8f896ab93583f974a7a471f8da20ba37c0fdb85f48e52b1be163cdeba3716b6acc0ca1
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  .bundle
2
2
  pkg
3
3
  .rvmrc
4
- Gemfile.lock
4
+ Gemfile.lock
5
+ spec/dummy/log/*.log
6
+ spec/dummy/tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.0
5
+ - 1.9.3
data/README.md CHANGED
@@ -1,106 +1,166 @@
1
1
  # Invisible Captcha
2
- Simple and flexible spam protection solution for Rails applications using honeypot strategy and for better user experience.
3
- Support for ActiveRecord (and ActiveModel) forms and for non-RESTful resources.
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/invisible_captcha.svg)](http://badge.fury.io/rb/invisible_captcha) [![Build Status](https://travis-ci.org/markets/invisible_captcha.svg)](https://travis-ci.org/markets/invisible_captcha)
4
+
5
+ Simple and flexible spam protection solution for Rails applications. Based on the `honeypot` strategy to provide a better user experience.
6
+
7
+ **Background**
8
+
9
+ The strategy is based on adding an input field into the form that:
10
+
11
+ * shouldn't be visible by the real users
12
+ * should be left empty by the real users
13
+ * will most be filled by spam bots
4
14
 
5
15
  ## Installation
16
+
6
17
  Add this line to you Gemfile:
7
18
 
8
19
  ```
9
20
  gem 'invisible_captcha'
10
21
  ```
11
22
 
12
- Or install gem manually:
23
+ Or install the gem manually:
13
24
 
14
25
  ```
15
- gem install invisible_captcha
26
+ $ gem install invisible_captcha
16
27
  ```
17
28
 
18
29
  ## Usage
19
- There are different ways to implement:
20
30
 
21
- ### Model style
31
+ There are different ways to implement, at Controller level or Model level:
32
+
33
+ ### Controller style
34
+
22
35
  View code:
23
36
 
24
37
  ```erb
25
- <%= form_for(@topic) do |f| %>
26
- <%= f.invisible_captcha :subtitle %>
38
+ <%= form_tag(create_topic_path) do |f| %>
39
+ <%= invisible_captcha %>
27
40
  <% end %>
28
41
  ```
29
42
 
30
- Model code:
43
+ Controller code:
31
44
 
32
45
  ```ruby
33
- class Topic < ActiveRecord::Base
34
- attr_accessor :subtitle # virtual attribute, the honeypot
35
- validates :subtitle, :invisible_captcha => true
46
+ class TopicsController < ApplicationController
47
+ invisible_captcha only: [:create, :update]
36
48
  end
37
49
  ```
38
50
 
39
- If you are using [strong_parameters](https://github.com/rails/strong_parameters), don't forget to keep the honeypot attribute into the params hash:
51
+ This method will act as a `before_filter` that triggers when spam is detected (honeypot field has some value). By default it responds with no content (only headers: `head(200)`). But you are able to define your own callback by passing a method to the `on_spam` option:
52
+
40
53
  ```ruby
41
- def topic_params
42
- params.require(:topic).permit(:subtitle)
54
+ invisible_captcha only: [:create, :update], on_spam: :your_on_spam_callback_method
55
+
56
+ private
57
+
58
+ def your_on_spam_callback_method
59
+ redirect_to root_path
43
60
  end
44
61
  ```
45
62
 
46
- ### Controller style
47
- View code:
63
+ [Check here a complete list of allowed options.](#controller-method-options)
64
+
65
+ ### Controller style (resource oriented):
66
+
67
+ In your form:
48
68
 
49
69
  ```erb
50
70
  <%= form_for(@topic) do |f| %>
51
- <%= invisible_captcha %>
71
+ <%= f.invisible_captcha :subtitle %>
72
+ <!-- or -->
73
+ <%= invisible_captcha :subtitle, :topic %>
52
74
  <% end %>
53
75
  ```
54
76
 
55
- Controller code:
77
+ In your controller:
56
78
 
57
79
  ```ruby
58
- class TopicsController < ApplicationController
59
- before_filter :check_invisible_captcha # :only => [:create, :update]
60
- # your controller code
61
- end
80
+ invisible_captcha only: [:create, :update], honeypot: :subtitle
62
81
  ```
63
82
 
64
- This filter triggers when the spam is the in params and responds without content (only headers). If you desire a different behaviour, you can use a provided method to check manualy if invisible captcha (fake field) is present:
65
-
66
- ```ruby
67
- if invisible_captcha?
68
- # spam present
69
- else
70
- # no spam
71
- end
72
- ```
83
+ ### Model style
73
84
 
74
- ### Controller style (resource oriented):
85
+ View code:
75
86
 
76
- In your form:
77
87
  ```erb
78
88
  <%= form_for(@topic) do |f| %>
79
89
  <%= f.invisible_captcha :subtitle %>
80
90
  <% end %>
81
91
  ```
82
92
 
83
- In your controller:
93
+ Model code:
94
+
84
95
  ```ruby
85
- def create
86
- if invisible_captcha?(:topic, :subtitle)
87
- head 200 # or redirect_to new_topic_path
88
- else
89
- # regular workflow
90
- end
96
+ class Topic < ActiveRecord::Base
97
+ attr_accessor :subtitle # define a virtual attribute, the honeypot
98
+ validates :subtitle, :invisible_captcha => true
91
99
  end
92
100
  ```
93
101
 
94
- ### Setup
95
- If you want to customize some defaults, add the following to an initializer (config/initializers/invisible_captcha.rb):
102
+ If you are using [strong_parameters](https://github.com/rails/strong_parameters) (by default in Rails 4), don't forget to keep the honeypot attribute into the params hash:
103
+
104
+ ```ruby
105
+ def topic_params
106
+ params.require(:topic).permit(:subtitle)
107
+ end
108
+ ```
109
+
110
+ ## Options and customization
111
+
112
+ This section contains the option list of `invisible_captcha` method (controllers side) and the plugin setup options (initializer).
113
+
114
+ ### Controller method options:
115
+
116
+ The `invisible_captcha` method accepts some options:
117
+
118
+ * `only`: apply to given controller actions.
119
+ * `except`: exclude to given controller actions.
120
+ * `honeypot`: name of honeypot.
121
+ * `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
122
+ * `on_spam`: custom callback to be called on spam detection.
123
+
124
+ ### Plugin options:
125
+
126
+ You also can customize some plugin options:
127
+
128
+ * `sentence_for_humans`: text for real users if input field was visible.
129
+ * `error_message`: error message thrown by model validation (only model implementation).
130
+ * `honeypots`: collection of default honeypots, used by the view helper, called with no args, to generate the honeypot field name
131
+ * `visual_honeypots`: make honeypots visible, useful to test/debug your implementation.
132
+
133
+ To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
96
134
 
97
135
  ```ruby
98
- InvisibleCaptcha.setup do |ic|
99
- ic.sentence_for_humans = 'If you are a human, ignore this field'
100
- ic.error_message = 'You are a robot!'
101
- ic.fake_fields << 'another_fake_field'
136
+ InvisibleCaptcha.setup do |config|
137
+ config.sentence_for_humans = 'If you are a human, ignore this field'
138
+ config.error_message = 'You are a robot!'
139
+ config.honeypots += 'fake_resource_title'
140
+ config.visual_honeypots = false
102
141
  end
103
142
  ```
104
143
 
144
+ ## Contribute
145
+
146
+ Any kind of idea, feedback or bug report are welcome! Open an [issue](https://github.com/markets/invisible_captcha/issues) or send a [pull request](https://github.com/markets/invisible_captcha/pulls).
147
+
148
+ ## Development
149
+
150
+ Clone/fork this repository and start to hack.
151
+
152
+ Run test suite:
153
+
154
+ ```
155
+ $ rspec
156
+ ```
157
+
158
+ Start a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:
159
+
160
+ ```
161
+ $ rake web # PORT=4000 (default: 3000)
162
+ ```
163
+
105
164
  ## License
165
+
106
166
  Copyright (c) 2012-2014 Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.
data/Rakefile CHANGED
@@ -1 +1,17 @@
1
- require "bundler/gem_tasks"
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc 'Start development Rails app'
9
+ task :web do
10
+ app_path = 'spec/dummy'
11
+ port = ENV['PORT'] || 3000
12
+
13
+ puts "Starting application in http://localhost:#{port} ... \n"
14
+
15
+ Dir.chdir(app_path)
16
+ `rails s -p #{port}`
17
+ end
@@ -16,8 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_dependency "activemodel", ">= 0"
19
+ spec.add_dependency 'rails'
20
20
 
21
- spec.add_development_dependency "debugger"
21
+ spec.add_development_dependency 'rspec-rails', '~> 3.1'
22
22
  end
23
23
 
@@ -1,32 +1,36 @@
1
1
  require 'invisible_captcha/version'
2
- require 'invisible_captcha/controller_methods'
2
+ require 'invisible_captcha/controller_ext'
3
3
  require 'invisible_captcha/view_helpers'
4
4
  require 'invisible_captcha/form_helpers'
5
5
  require 'invisible_captcha/validator'
6
+ require 'invisible_captcha/railtie'
6
7
 
7
8
  module InvisibleCaptcha
8
- # Default sentence for humans if text field is visible
9
- mattr_accessor :sentence_for_humans
10
- self.sentence_for_humans = 'If you are a human, ignore this field'
11
-
12
- # Default error message for validator
13
- mattr_accessor :error_message
14
- self.error_message = 'You are a robot!'
15
-
16
- # Default fake fields for controller based workflow
17
- mattr_accessor :fake_fields
18
- self.fake_fields = ['foo_id', 'bar_id', 'baz_id']
19
-
20
- # InvisibleCaptcha.setup do |ic|
21
- # ic.sentence_for_humans = 'Another sentence'
22
- # ic.error_message = 'Another error message'
23
- # ic.fake_fields << 'another_fake_field'
24
- # end
25
- def self.setup
26
- yield(self)
27
- end
9
+ class << self
10
+ attr_accessor :sentence_for_humans, :error_message, :honeypots, :visual_honeypots
11
+
12
+ def init!
13
+ # Default sentence for real users if text field was visible
14
+ self.sentence_for_humans = 'If you are a human, ignore this field'
15
+
16
+ # Default error message for validator
17
+ self.error_message = 'You are a robot!'
18
+
19
+ # Default fake fields for controller based workflow
20
+ self.honeypots = ['foo_id', 'bar_id', 'baz_id']
28
21
 
29
- def self.fake_field
30
- self.fake_fields.sample
22
+ # Make honeypots visibles
23
+ self.visual_honeypots = false
24
+ end
25
+
26
+ def setup
27
+ yield(self) if block_given?
28
+ end
29
+
30
+ def get_honeypot
31
+ honeypots.sample
32
+ end
31
33
  end
32
- end
34
+ end
35
+
36
+ InvisibleCaptcha.init!
@@ -0,0 +1,48 @@
1
+ module InvisibleCaptcha
2
+ module ControllerExt
3
+ module ClassMethods
4
+ def invisible_captcha(options = {})
5
+ before_filter(options) do
6
+ detect_spam(options)
7
+ end
8
+ end
9
+ end
10
+
11
+ def detect_spam(options = {})
12
+ if invisible_captcha?(options)
13
+ on_spam_action(options)
14
+ end
15
+ end
16
+
17
+ def on_spam_action(options = {})
18
+ if action = options[:on_spam]
19
+ send(action)
20
+ else
21
+ default_on_spam
22
+ end
23
+ end
24
+
25
+ def default_on_spam
26
+ head(200)
27
+ end
28
+
29
+ def invisible_captcha?(options = {})
30
+ honeypot = options[:honeypot]
31
+ scope = options[:scope] || controller_name.singularize
32
+
33
+ if honeypot
34
+ # If honeypot is presented, search for:
35
+ # - honeypot: params[:subtitle]
36
+ # - honeypot with scope: params[:topic][:subtitle]
37
+ if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
38
+ return true
39
+ end
40
+ else
41
+ InvisibleCaptcha.honeypots.each do |field|
42
+ return true if params[field].present?
43
+ end
44
+ end
45
+ false
46
+ end
47
+ end
48
+ end
@@ -1,11 +1,7 @@
1
1
  module InvisibleCaptcha
2
2
  module FormHelpers
3
-
4
- def invisible_captcha(method)
5
- @template.invisible_captcha(self.object_name, method)
3
+ def invisible_captcha(honeypot)
4
+ @template.invisible_captcha(honeypot, self.object_name)
6
5
  end
7
-
8
6
  end
9
- end
10
-
11
- ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
7
+ end
@@ -0,0 +1,9 @@
1
+ module InvisibleCaptcha
2
+ class Railtie < Rails::Railtie
3
+ ActionController::Base.send :include, InvisibleCaptcha::ControllerExt
4
+ ActionController::Base.send :extend, InvisibleCaptcha::ControllerExt::ClassMethods
5
+ ActionView::Base.send :include, InvisibleCaptcha::ViewHelpers
6
+ ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
7
+ ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
8
+ end
9
+ end
@@ -2,7 +2,6 @@ require 'active_model/validator'
2
2
 
3
3
  module InvisibleCaptcha
4
4
  class InvisibleCaptchaValidator < ActiveModel::EachValidator
5
-
6
5
  def validate_each(record, attribute, value)
7
6
  if invisible_captcha?(record, attribute)
8
7
  record.errors.clear
@@ -12,11 +11,8 @@ module InvisibleCaptcha
12
11
 
13
12
  private
14
13
 
15
- def invisible_captcha?(object, attribute)
16
- object.send(attribute).present?
14
+ def invisible_captcha?(object, honeypot)
15
+ object.send(honeypot).present?
17
16
  end
18
-
19
17
  end
20
- end
21
-
22
- ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
18
+ end
@@ -1,3 +1,3 @@
1
1
  module InvisibleCaptcha
2
- VERSION = "0.6.5"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -1,50 +1,52 @@
1
1
  module InvisibleCaptcha
2
2
  module ViewHelpers
3
-
4
- def invisible_captcha(resource = nil, method = nil)
5
- build_invisible_captcha(resource, method)
3
+ # Builds the honeypot html
4
+ #
5
+ # @param honeypot [Symbol] name of honeypot, ie: subtitle => input name: subtitle
6
+ # @param scope [Symbol] name of honeypot scope, ie: topic => input name: topic[subtitle]
7
+ # @return [String] the generated html
8
+ def invisible_captcha(honeypot = nil, scope = nil)
9
+ build_invisible_captcha(honeypot, scope)
6
10
  end
7
11
 
8
12
  private
9
13
 
10
- def build_invisible_captcha(resource = nil, method = nil)
11
- resource = resource ? resource.to_s : InvisibleCaptcha.fake_field
12
- label = InvisibleCaptcha.sentence_for_humans
13
- html_id = generate_html_id(resource)
14
+ def build_invisible_captcha(honeypot = nil, scope = nil)
15
+ honeypot = honeypot ? honeypot.to_s : InvisibleCaptcha.get_honeypot
16
+ label = InvisibleCaptcha.sentence_for_humans
17
+ html_id = generate_html_id(honeypot, scope)
14
18
 
15
19
  content_tag(:div, :id => html_id) do
16
20
  insert_inline_css(html_id) +
17
- label_tag(build_label_name(resource, method), label) +
18
- text_field_tag(build_text_field_name(resource, method))
21
+ label_tag(build_label_name(honeypot, scope), label) +
22
+ text_field_tag(build_text_field_name(honeypot, scope))
19
23
  end.html_safe
20
24
  end
21
25
 
22
- def generate_html_id(resource)
23
- "#{resource}_#{Time.now.to_i}"
26
+ def generate_html_id(honeypot, scope = nil)
27
+ "#{scope || honeypot}_#{Time.now.to_i}"
24
28
  end
25
29
 
26
30
  def insert_inline_css(container_id)
27
31
  content_tag(:style, :type => 'text/css', :media => 'screen', :scoped => 'scoped') do
28
- "##{container_id} { display:none; }"
32
+ "##{container_id} { display:none; }" unless InvisibleCaptcha.visual_honeypots
29
33
  end
30
34
  end
31
35
 
32
- def build_label_name(resource, method = nil)
33
- if method.present?
34
- "#{resource}_#{method}"
36
+ def build_label_name(honeypot, scope = nil)
37
+ if scope.present?
38
+ "#{scope}_#{honeypot}"
35
39
  else
36
- resource
40
+ honeypot
37
41
  end
38
42
  end
39
43
 
40
- def build_text_field_name(resource, method = nil)
41
- if method.present?
42
- "#{resource}[#{method}]"
44
+ def build_text_field_name(honeypot, scope = nil)
45
+ if scope.present?
46
+ "#{scope}[#{honeypot}]"
43
47
  else
44
- resource
48
+ honeypot
45
49
  end
46
50
  end
47
51
  end
48
- end
49
-
50
- ActionView::Base.send :include, InvisibleCaptcha::ViewHelpers
52
+ end