invisible_captcha 0.8.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -2
  5. data/Appraisals +11 -0
  6. data/LICENSE +1 -1
  7. data/README.md +62 -63
  8. data/gemfiles/rails_3.2.gemfile +7 -0
  9. data/gemfiles/rails_4.1.gemfile +7 -0
  10. data/gemfiles/rails_4.2.gemfile +7 -0
  11. data/invisible_captcha.gemspec +5 -2
  12. data/lib/invisible_captcha/controller_ext.rb +35 -2
  13. data/lib/invisible_captcha/railtie.rb +1 -3
  14. data/lib/invisible_captcha/version.rb +1 -1
  15. data/lib/invisible_captcha/view_helpers.rb +5 -2
  16. data/lib/invisible_captcha.rb +38 -5
  17. data/spec/controllers_spec.rb +84 -11
  18. data/spec/dummy/app/controllers/topics_controller.rb +12 -1
  19. data/spec/dummy/app/models/topic.rb +0 -1
  20. data/spec/dummy/bin/bundle +3 -0
  21. data/spec/dummy/bin/rails +4 -0
  22. data/spec/dummy/bin/rake +4 -0
  23. data/spec/dummy/bin/setup +29 -0
  24. data/spec/dummy/config/application.rb +16 -5
  25. data/spec/dummy/config/boot.rb +2 -4
  26. data/spec/dummy/config/environment.rb +3 -3
  27. data/spec/dummy/config/environments/development.rb +23 -12
  28. data/spec/dummy/config/environments/production.rb +45 -33
  29. data/spec/dummy/config/environments/test.rb +19 -11
  30. data/spec/dummy/config/initializers/assets.rb +11 -0
  31. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  32. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/spec/dummy/config/initializers/inflections.rb +6 -5
  34. data/spec/dummy/config/initializers/mime_types.rb +0 -1
  35. data/spec/dummy/config/initializers/session_store.rb +1 -6
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +7 -2
  37. data/spec/dummy/config/locales/en.yml +20 -2
  38. data/spec/dummy/config/routes.rb +6 -2
  39. data/spec/dummy/config/secrets.yml +22 -0
  40. data/spec/invisible_captcha_spec.rb +36 -1
  41. data/spec/spec_helper.rb +1 -1
  42. data/spec/view_helpers_spec.rb +22 -4
  43. metadata +67 -9
  44. data/lib/invisible_captcha/validator.rb +0 -15
  45. data/spec/dummy/script/rails +0 -5
  46. data/spec/validator_spec.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03aab9758bfbaf12adb3913f5b37c8423d269c2e
4
- data.tar.gz: 18e136fc292c192126f7c08d5d60286a4c86ba6b
3
+ metadata.gz: 6667c856fdf4fb2ae0147d0d2d3d0531f8fab862
4
+ data.tar.gz: 7d2dadae6a9010c3614166343221fc0064fe9bc2
5
5
  SHA512:
6
- metadata.gz: c60cc1997f5748fef444f1eea60aa3a47b42ee6de60dffa8f320e346608e224594f4252efbe19d7df80edaf8c137729df4357162cb27f2e8b6c7fa1327aed94f
7
- data.tar.gz: b5b62112cf6ea24aab60e66f589a0756d06e7337c7564e43cdea6a838023334c43e22cba5e486c3c8b1278fe1ef4829a5c925fea3e4917ba919677bd10710049
6
+ metadata.gz: a76b88b892d95a1d72fcf5f2bffb2165c0608554feab107ca5a61e25db4a6b2e4eb3349ecba9b7e0c817d2766c23bb6ebd6723fa20717f8b3518ea38583ebab8
7
+ data.tar.gz: b15a66cfab3ec429aaafcdcd310e66f178cf17ab1375d58f0f0454b75b00059600295d5b54df404630ba8ceb0d596a84b6f688312bdeb2c04e696d7d211ddf04
data/.gitignore CHANGED
@@ -1,6 +1,10 @@
1
1
  .bundle
2
2
  pkg
3
3
  .rvmrc
4
+ .ruby-version
5
+ .ruby-gemset
4
6
  Gemfile.lock
7
+ *.gemfile.lock
5
8
  spec/dummy/log/*.log
6
- spec/dummy/tmp/
9
+ spec/dummy/tmp/
10
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml CHANGED
@@ -1,5 +1,16 @@
1
1
  language: ruby
2
+
3
+ cache: bundler
4
+
5
+ sudo: false
6
+
2
7
  rvm:
8
+ - 2.3.0
9
+ - 2.2
3
10
  - 2.1
4
- - 2.0
5
- - 1.9.3
11
+ - 1.9.3
12
+
13
+ gemfile:
14
+ - gemfiles/rails_4.2.gemfile
15
+ - gemfiles/rails_4.1.gemfile
16
+ - gemfiles/rails_3.2.gemfile
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise "rails-4.2" do
2
+ gem "rails", "~> 4.2.0"
3
+ end
4
+
5
+ appraise "rails-4.1" do
6
+ gem "rails", "~> 4.1.0"
7
+ end
8
+
9
+ appraise "rails-3.2" do
10
+ gem "rails", "~> 3.2.0"
11
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2015 Marc Anguera Insa
1
+ Copyright 2012-2016 Marc Anguera Insa
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
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
4
 
5
- Simple and flexible spam protection solution for Rails applications. Based on the `honeypot` strategy to provide a better user experience.
5
+ > Simple and flexible spam protection solution for Rails applications.
6
+
7
+ It is based on the `honeypot` strategy to provide a better user experience. It also provides a time-sensitive form submission.
6
8
 
7
9
  **Background**
8
10
 
9
- The strategy is based on adding an input field into the form that:
11
+ The strategy is about adding an input field into the form that:
10
12
 
11
13
  * shouldn't be visible by the real users
12
14
  * should be left empty by the real users
@@ -14,6 +16,8 @@ The strategy is based on adding an input field into the form that:
14
16
 
15
17
  ## Installation
16
18
 
19
+ Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 1.9.3`.
20
+
17
21
  Add this line to you Gemfile:
18
22
 
19
23
  ```
@@ -28,15 +32,13 @@ $ gem install invisible_captcha
28
32
 
29
33
  ## Usage
30
34
 
31
- There are different ways to implement, at Controller level or Model level:
32
-
33
- ### Controller style
34
-
35
35
  View code:
36
36
 
37
37
  ```erb
38
- <%= form_tag(create_topic_path) do |f| %>
39
- <%= invisible_captcha %>
38
+ <%= form_for(@topic) do |f| %>
39
+ <%= f.invisible_captcha :subtitle %>
40
+ <!-- or -->
41
+ <%= invisible_captcha :subtitle, :topic %>
40
42
  <% end %>
41
43
  ```
42
44
 
@@ -44,67 +46,36 @@ Controller code:
44
46
 
45
47
  ```ruby
46
48
  class TopicsController < ApplicationController
47
- invisible_captcha only: [:create, :update]
49
+ invisible_captcha only: [:create, :update], honeypot: :subtitle
48
50
  end
49
51
  ```
50
52
 
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:
53
+ 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)`). This is a good default, since the bot will surely read the response code and will think that it has achieved to submit the form properly. But, anyway, you are able to define your own callback by passing a method to the `on_spam` option:
52
54
 
53
55
  ```ruby
54
- invisible_captcha only: [:create, :update], on_spam: :your_on_spam_callback_method
56
+ class TopicsController < ApplicationController
57
+ invisible_captcha only: [:create, :update], on_spam: :your_spam_callback_method
55
58
 
56
- private
59
+ private
57
60
 
58
- def your_on_spam_callback_method
59
- redirect_to root_path
61
+ def your_spam_callback_method
62
+ redirect_to root_path
63
+ end
60
64
  end
61
65
  ```
62
66
 
63
- [Check here a complete list of allowed options.](#controller-method-options)
64
-
65
- ### Controller style (resource oriented):
66
-
67
- In your form:
68
-
69
- ```erb
70
- <%= form_for(@topic) do |f| %>
71
- <%= f.invisible_captcha :subtitle %>
72
- <!-- or -->
73
- <%= invisible_captcha :subtitle, :topic %>
74
- <% end %>
75
- ```
76
-
77
- In your controller:
78
-
79
- ```ruby
80
- invisible_captcha only: [:create, :update], honeypot: :subtitle
81
- ```
82
-
83
- ### Model style
84
-
85
- View code:
67
+ Note that isn't mandatory to specify a `honeypot` attribute (nor in the view, nor in the controller). In this case, the engine will take a random field from `InvisibleCaptcha.honeypots`. So, if you're integrating it following this path, in your form:
86
68
 
87
69
  ```erb
88
- <%= form_for(@topic) do |f| %>
89
- <%= f.invisible_captcha :subtitle %>
70
+ <%= form_tag(new_contact_path) do |f| %>
71
+ <%= invisible_captcha %>
90
72
  <% end %>
91
73
  ```
92
74
 
93
- Model code:
75
+ In you controller:
94
76
 
95
- ```ruby
96
- class Topic < ActiveRecord::Base
97
- attr_accessor :subtitle # define a virtual attribute, the honeypot
98
- validates :subtitle, :invisible_captcha => true
99
- end
100
77
  ```
101
-
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
78
+ invisible_captcha only: [:new_contact]
108
79
  ```
109
80
 
110
81
  ## Options and customization
@@ -115,19 +86,24 @@ This section contains a description of all plugin options and customizations.
115
86
 
116
87
  You can customize:
117
88
 
118
- * `sentence_for_humans`: text for real users if input field was visible.
119
- * `error_message`: error message thrown by model validation (only model implementation).
120
- * `honeypots`: collection of default honeypots, used by the view helper, called with no args, to generate the honeypot field name
89
+ * `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).
90
+ * `honeypots`: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name.
121
91
  * `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
92
+ * `timestamp_threshold`: fastest time (in seconds) to expect a human to submit the form (see [original article by Yoav Aner](http://blog.gingerlime.com/2012/simple-detection-of-comment-spam-in-rails/) outlining the idea). By default, 4 seconds. **NOTE:** It's recommended to deactivate the autocomplete feature to avoid false positives (`autocomplete="off"`).
93
+ * `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
94
+ * `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.
122
95
 
123
96
  To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
124
97
 
125
98
  ```ruby
126
99
  InvisibleCaptcha.setup do |config|
127
- config.sentence_for_humans = 'If you are a human, ignore this field'
128
- config.error_message = 'You are a robot!'
129
- config.honeypots += 'fake_resource_title'
100
+ config.honeypots << 'another_fake_attribute'
130
101
  config.visual_honeypots = false
102
+ config.timestamp_threshold = 4
103
+ config.timestamp_enabled = true
104
+ # Leave these unset if you want to use I18n (see below)
105
+ # config.sentence_for_humans = 'If you are a human, ignore this field'
106
+ # config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.'
131
107
  end
132
108
  ```
133
109
 
@@ -140,6 +116,8 @@ The `invisible_captcha` method accepts some options:
140
116
  * `honeypot`: name of honeypot.
141
117
  * `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
142
118
  * `on_spam`: custom callback to be called on spam detection.
119
+ * `on_timestamp_spam`: custom callback to be called when form submitted too quickly. The default action redirects to `:back` printing a warning in `flash[:error]`.
120
+ * `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.
143
121
 
144
122
  ### View helpers options:
145
123
 
@@ -153,26 +131,47 @@ Using the view/form helper you can override some defaults for the given instance
153
131
  <% end %>
154
132
  ```
155
133
 
134
+ ### I18n
135
+
136
+ `invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
137
+
138
+ ```yaml
139
+ en:
140
+ invisible_captcha:
141
+ sentence_for_humans: "If you are human, ignore this field"
142
+ timestamp_error_message: "Sorry, that was too quick! Please resubmit."
143
+ ```
144
+
145
+ You can override the english ones in your own i18n config files as well as add new ones for other locales.
146
+
147
+ If you intend to use I18n with `invisible_captcha`, you _must not_ set `sentence_for_humans` or `timestamp_error_message` to strings in the setup phase.
148
+
156
149
  ## Contribute
157
150
 
158
151
  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).
159
152
 
160
153
  ## Development
161
154
 
162
- Clone/fork this repository and start to hack.
155
+ Clone/fork this repository, start to hack on it and send a pull request.
156
+
157
+ Run the test suite:
158
+
159
+ ```
160
+ $ bundle exec rspec
161
+ ```
163
162
 
164
- Run test suite:
163
+ Run the test suite against all supported versions:
165
164
 
166
165
  ```
167
- $ rspec
166
+ $ bundle exec appraisal rake
168
167
  ```
169
168
 
170
169
  Start a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:
171
170
 
172
171
  ```
173
- $ rake web # PORT=4000 (default: 3000)
172
+ $ bundle exec rake web # PORT=4000 (default: 3000)
174
173
  ```
175
174
 
176
175
  ## License
177
176
 
178
- Copyright (c) 2012-2015 Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.
177
+ Copyright (c) 2012-2016 Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 3.2.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.2.0"
6
+
7
+ gemspec :path => "../"
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
6
6
  spec.version = InvisibleCaptcha::VERSION
7
7
  spec.authors = ["Marc Anguera Insa"]
8
8
  spec.email = ["srmarc.ai@gmail.com"]
9
- spec.description = %q{Simple spam protection for Rails applications using honeypot strategy for better user experience.}
10
- spec.summary = %q{Simple honeypot protection for RoR apps}
9
+ spec.description = "Unobtrusive, flexible and simple spam protection for Rails applications using honeypot strategy for better user experience."
10
+ spec.summary = "Simple honeypot protection for RoR apps"
11
11
  spec.homepage = "https://github.com/markets/invisible_captcha"
12
12
  spec.license = "MIT"
13
13
 
@@ -19,5 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.add_dependency 'rails'
20
20
 
21
21
  spec.add_development_dependency 'rspec-rails', '~> 3.1'
22
+ spec.add_development_dependency 'appraisal'
23
+ spec.add_development_dependency 'test-unit', '~> 3.0'
24
+ spec.add_development_dependency 'mime-types', '< 3.0'
22
25
  end
23
26
 
@@ -9,11 +9,21 @@ module InvisibleCaptcha
9
9
  end
10
10
 
11
11
  def detect_spam(options = {})
12
- if invisible_captcha?(options)
12
+ if invisible_captcha_timestamp?(options)
13
+ on_timestamp_spam_action(options)
14
+ elsif invisible_captcha?(options)
13
15
  on_spam_action(options)
14
16
  end
15
17
  end
16
18
 
19
+ def on_timestamp_spam_action(options = {})
20
+ if action = options[:on_timestamp_spam]
21
+ send(action)
22
+ else
23
+ redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
24
+ end
25
+ end
26
+
17
27
  def on_spam_action(options = {})
18
28
  if action = options[:on_spam]
19
29
  send(action)
@@ -26,6 +36,29 @@ module InvisibleCaptcha
26
36
  head(200)
27
37
  end
28
38
 
39
+ def invisible_captcha_timestamp?(options = {})
40
+ unless InvisibleCaptcha.timestamp_enabled
41
+ return false
42
+ end
43
+
44
+ timestamp = session[:invisible_captcha_timestamp]
45
+
46
+ # Consider as spam if timestamp not in session, cause that means the form was not fetched at all
47
+ unless timestamp
48
+ logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp not found in session.")
49
+ return true
50
+ end
51
+
52
+ time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
53
+
54
+ # Consider as spam if form submitted too quickly
55
+ if time_to_submit < (options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold)
56
+ logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
57
+ return true
58
+ end
59
+ false
60
+ end
61
+
29
62
  def invisible_captcha?(options = {})
30
63
  honeypot = options[:honeypot]
31
64
  scope = options[:scope] || controller_name.singularize
@@ -45,4 +78,4 @@ module InvisibleCaptcha
45
78
  false
46
79
  end
47
80
  end
48
- end
81
+ end
@@ -10,8 +10,6 @@ module InvisibleCaptcha
10
10
  include InvisibleCaptcha::ViewHelpers
11
11
  ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
12
12
  end
13
-
14
- ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
15
13
  end
16
14
  end
17
- end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module InvisibleCaptcha
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -6,6 +6,9 @@ module InvisibleCaptcha
6
6
  # @param scope [Symbol] name of honeypot scope, ie: topic => input name: topic[subtitle]
7
7
  # @return [String] the generated html
8
8
  def invisible_captcha(honeypot = nil, scope = nil, options = {})
9
+ if InvisibleCaptcha.timestamp_enabled
10
+ session[:invisible_captcha_timestamp] ||= Time.zone.now.iso8601
11
+ end
9
12
  build_invisible_captcha(honeypot, scope, options)
10
13
  end
11
14
 
@@ -29,7 +32,7 @@ module InvisibleCaptcha
29
32
  end
30
33
 
31
34
  def generate_html_id(honeypot, scope = nil)
32
- "#{scope || honeypot}_#{Time.now.to_i}"
35
+ "#{scope || honeypot}_#{Time.zone.now.to_i}"
33
36
  end
34
37
 
35
38
  def visibility_css(container_id, options)
@@ -60,4 +63,4 @@ module InvisibleCaptcha
60
63
  end
61
64
  end
62
65
  end
63
- end
66
+ end
@@ -2,27 +2,54 @@ require 'invisible_captcha/version'
2
2
  require 'invisible_captcha/controller_ext'
3
3
  require 'invisible_captcha/view_helpers'
4
4
  require 'invisible_captcha/form_helpers'
5
- require 'invisible_captcha/validator'
6
5
  require 'invisible_captcha/railtie'
7
6
 
8
7
  module InvisibleCaptcha
9
8
  class << self
10
- attr_accessor :sentence_for_humans, :error_message, :honeypots, :visual_honeypots
9
+ attr_writer :sentence_for_humans,
10
+ :timestamp_error_message,
11
+ :error_message
12
+
13
+ attr_accessor :honeypots,
14
+ :timestamp_threshold,
15
+ :timestamp_enabled,
16
+ :visual_honeypots
11
17
 
12
18
  def init!
13
19
  # Default sentence for real users if text field was visible
14
- self.sentence_for_humans = 'If you are a human, ignore this field'
20
+ self.sentence_for_humans = -> { I18n.t('invisible_captcha.sentence_for_humans', default: 'If you are a human, ignore this field') }
15
21
 
16
22
  # Default error message for validator
17
- self.error_message = 'You are a robot!'
23
+ self.error_message = -> { I18n.t('invisible_captcha.error_message', default: 'You are a robot!') }
18
24
 
19
25
  # Default fake fields for controller based workflow
20
26
  self.honeypots = ['foo_id', 'bar_id', 'baz_id']
21
27
 
28
+ # Fastest time (in seconds) to expect a human to submit the form
29
+ self.timestamp_threshold = 4
30
+
31
+ # Timestamp check enabled by default
32
+ self.timestamp_enabled = true
33
+
34
+ # Default error message for validator when form submitted too quickly
35
+ self.timestamp_error_message = -> { I18n.t('invisible_captcha.timestamp_error_message', default: 'Sorry, that was too quick! Please resubmit.') }
36
+
22
37
  # Make honeypots visibles
23
38
  self.visual_honeypots = false
24
39
  end
25
40
 
41
+ def sentence_for_humans
42
+ call_lambda_or_return(@sentence_for_humans)
43
+ end
44
+
45
+ def error_message
46
+ call_lambda_or_return(@error_message)
47
+ end
48
+
49
+ def timestamp_error_message
50
+ call_lambda_or_return(@timestamp_error_message)
51
+ end
52
+
26
53
  def setup
27
54
  yield(self) if block_given?
28
55
  end
@@ -30,7 +57,13 @@ module InvisibleCaptcha
30
57
  def get_honeypot
31
58
  honeypots.sample
32
59
  end
60
+
61
+ private
62
+
63
+ def call_lambda_or_return(obj)
64
+ obj.respond_to?(:call) ? obj.call : obj
65
+ end
33
66
  end
34
67
  end
35
68
 
36
- InvisibleCaptcha.init!
69
+ InvisibleCaptcha.init!
@@ -3,23 +3,96 @@ require 'spec_helper'
3
3
  describe InvisibleCaptcha::ControllerExt, type: :controller do
4
4
  render_views
5
5
 
6
- before { @controller = TopicsController.new }
6
+ before do
7
+ @controller = TopicsController.new
8
+ InvisibleCaptcha.timestamp_threshold = 1
9
+ InvisibleCaptcha.timestamp_enabled = true
10
+ end
7
11
 
8
- it 'with spam' do
9
- post :create, topic: { subtitle: 'foo' }
12
+ context 'without invisible_captcha_timestamp in session' do
13
+ it 'fails like if it was submitted too fast' do
14
+ request.env['HTTP_REFERER'] = 'http://test.host/topics'
15
+ post :create, topic: { title: 'foo' }
10
16
 
11
- expect(response.body).to be_blank
17
+ expect(response).to redirect_to :back
18
+ expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
19
+ end
12
20
  end
13
21
 
14
- it 'with no spam' do
15
- post :create, topic: { title: 'foo' }
22
+ context 'without invisible_captcha_timestamp in session and timestamp_enabled=false' do
23
+ it 'does not fail like if it was submitted too fast' do
24
+ request.env['HTTP_REFERER'] = 'http://test.host/topics'
25
+ InvisibleCaptcha.timestamp_enabled = false
26
+ post :create, topic: { title: 'foo' }
27
+
28
+ expect(flash[:error]).not_to be_present
29
+ expect(response.body).to be_present
30
+ end
31
+ end
32
+
33
+ context 'submission timestamp_threshold' do
34
+ before do
35
+ session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
36
+ end
37
+
38
+ it 'fails if submission before timestamp_threshold' do
39
+ request.env['HTTP_REFERER'] = 'http://test.host/topics'
40
+ post :create, topic: { title: 'foo' }
41
+
42
+ expect(response).to redirect_to :back
43
+ expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
44
+ end
45
+
46
+ it 'allow custom on_timestamp_spam callback' do
47
+ put :update, id: 1, topic: { title: 'bar' }
48
+
49
+ expect(response).to redirect_to(root_path)
50
+ end
51
+
52
+ context 'successful submissions' do
53
+ it 'passes if submission on or after timestamp_threshold' do
54
+ sleep InvisibleCaptcha.timestamp_threshold
16
55
 
17
- expect(response.body).to be_present
56
+ post :create, topic: { title: 'foo' }
57
+
58
+ expect(flash[:error]).not_to be_present
59
+ expect(response.body).to be_present
60
+ end
61
+
62
+ it 'allow to set a custom timestamp_threshold per action' do
63
+ sleep 2 # custom threshold
64
+
65
+ post :publish, id: 1
66
+
67
+ expect(flash[:error]).not_to be_present
68
+ expect(response.body).to be_present
69
+ end
70
+ end
18
71
  end
19
72
 
20
- it 'allow custom on_spam callback' do
21
- put :update, id: 1, topic: { subtitle: 'foo' }
73
+ context 'honeypot attribute' do
74
+ before do
75
+ session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
76
+ # Wait for valid submission
77
+ sleep InvisibleCaptcha.timestamp_threshold
78
+ end
79
+
80
+ it 'fails with spam' do
81
+ post :create, topic: { subtitle: 'foo' }
82
+
83
+ expect(response.body).to be_blank
84
+ end
85
+
86
+ it 'passes with no spam' do
87
+ post :create, topic: { title: 'foo' }
88
+
89
+ expect(response.body).to be_present
90
+ end
91
+
92
+ it 'allow custom on_spam callback' do
93
+ put :update, id: 1, topic: { subtitle: 'foo' }
22
94
 
23
- expect(response.body).to redirect_to(new_topic_path)
95
+ expect(response.body).to redirect_to(new_topic_path)
96
+ end
24
97
  end
25
- end
98
+ end
@@ -1,6 +1,9 @@
1
1
  class TopicsController < ApplicationController
2
2
  invisible_captcha honeypot: :subtitle, only: :create
3
- invisible_captcha honeypot: :subtitle, only: :update, on_spam: :custom_callback
3
+ invisible_captcha honeypot: :subtitle, only: :update,
4
+ on_spam: :custom_callback,
5
+ on_timestamp_spam: :custom_timestamp_callback
6
+ invisible_captcha honeypot: :subtitle, only: :publish, timestamp_threshold: 2
4
7
 
5
8
  def new
6
9
  @topic = Topic.new
@@ -19,9 +22,17 @@ class TopicsController < ApplicationController
19
22
  def update
20
23
  end
21
24
 
25
+ def publish
26
+ redirect_to new_topic_path
27
+ end
28
+
22
29
  private
23
30
 
24
31
  def custom_callback
25
32
  redirect_to new_topic_path
26
33
  end
34
+
35
+ def custom_timestamp_callback
36
+ redirect_to root_path
37
+ end
27
38
  end
@@ -5,7 +5,6 @@ class Topic
5
5
  attr_accessor :title, :body, :subtitle
6
6
 
7
7
  validates :title, presence: true
8
- validates :subtitle, invisible_captcha: true
9
8
 
10
9
  def initialize(attributes = {})
11
10
  attributes.each do |name, value|
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run