invisible_captcha 0.12.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0904f2d5218d84ee62e3328259b217c7a3772497c8ecf47ed94a34b4bc083f8
4
- data.tar.gz: a2e806057b7cbe29927bbb705e342426b666cbc5c6bc823fe97434d912b322cc
3
+ metadata.gz: 0d931568dd6707074ae2b5f48f6a5552283d482a4d87f27b1c72852dfce3d9cb
4
+ data.tar.gz: 4e78f33d0a7c4be1a774de1e98e55d7fd2f7ff84e1a407d66a565c6e99779fa9
5
5
  SHA512:
6
- metadata.gz: 9f0ac495f41f5937859077d758e88884e369aa534d97b595fd4dc560923ce8cb9deb31bd3c487f33d04c82878eeed9ada45508b511e8ff77320d762979f0ea7d
7
- data.tar.gz: 953bfa53bf999143d76c45f785e11e80e8fbd44a20fc0182c6d87e009ece31610f9b699049e01e01b6fcfeb887de968a656f1dd437973a5d54e8d43cc6c170bb
6
+ metadata.gz: 3fbe8b6755bbc31fb26fbf9d71f01b7c9dbe919feaaaf12f78f86f7e53e93bb9f04f52c83469ea59840714908d834abf84625a542e7f55de2b7f0cd1d877986c
7
+ data.tar.gz: 380701e4ddf138445faafb293ed94fe368d58eb7d995d44b3f871362550d5c2bb27d537d00c04cdf89501bc951ab227d3fe7c721389ba1f163327f4d8093ced4
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ Gemfile.lock
8
8
  spec/dummy/log/*.log
9
9
  spec/dummy/tmp/
10
10
  .byebug_history
11
+ .DS_Store
@@ -1,20 +1,17 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- sudo: false
4
3
  rvm:
5
4
  - ruby-head
6
- - 2.6.2
7
- - 2.5.5
8
- - 2.4.5
5
+ - 2.6.5
6
+ - 2.5.7
7
+ - 2.4.9
9
8
  - 2.3.8
10
- - 2.2.10
11
9
  gemfile:
12
10
  - gemfiles/rails_6.0.gemfile
13
11
  - gemfiles/rails_5.2.gemfile
14
12
  - gemfiles/rails_5.1.gemfile
15
13
  - gemfiles/rails_5.0.gemfile
16
14
  - gemfiles/rails_4.2.gemfile
17
- - gemfiles/rails_3.2.gemfile
18
15
  before_install:
19
16
  # Rails 4.x requires Bundler version < 2.0.
20
17
  - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
@@ -22,19 +19,9 @@ before_install:
22
19
  - rvm @global do yes | gem install bundler -v '< 2'
23
20
  matrix:
24
21
  exclude:
25
- - rvm: 2.4.5
22
+ - rvm: 2.4.9
26
23
  gemfile: gemfiles/rails_6.0.gemfile
27
24
  - rvm: 2.3.8
28
25
  gemfile: gemfiles/rails_6.0.gemfile
29
- - rvm: 2.2.10
30
- gemfile: gemfiles/rails_6.0.gemfile
31
- - rvm: ruby-head
32
- gemfile: gemfiles/rails_3.2.gemfile
33
- - rvm: 2.6.2
34
- gemfile: gemfiles/rails_3.2.gemfile
35
- - rvm: 2.5.5
36
- gemfile: gemfiles/rails_3.2.gemfile
37
- - rvm: 2.4.5
38
- gemfile: gemfiles/rails_3.2.gemfile
39
26
  allow_failures:
40
- - rvm: ruby-head
27
+ - rvm: ruby-head
data/Appraisals CHANGED
@@ -1,5 +1,5 @@
1
1
  appraise "rails-6.0" do
2
- gem "rails", "6.0.0.rc1"
2
+ gem "rails", "~> 6.0.0"
3
3
  end
4
4
 
5
5
  appraise "rails-5.2" do
@@ -17,7 +17,3 @@ end
17
17
  appraise "rails-4.2" do
18
18
  gem "rails", "~> 4.2.0"
19
19
  end
20
-
21
- appraise "rails-3.2" do
22
- gem "rails", "~> 3.2.0"
23
- end
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.0]
6
+
7
+ - New option `prepend: true` for the controller macro (#77)
8
+
9
+ ## [1.0.1]
10
+
11
+ - Fix naming issue with Ruby 2.7 (#65)
12
+
13
+ ## [1.0.0]
14
+
15
+ - Remove Ruby 2.2 and Rails 3.2 support
16
+ - Add Instrumentation event (#62)
17
+
18
+ ## [0.13.0]
19
+
20
+ - Add support for the Content Security Policy nonce (#61)
21
+ - Freeze all strings (#60)
22
+
23
+ ## [0.12.2]
24
+
25
+ - Allow new timestamp to be set during `on_timestamp_spam` callback (#53)
26
+
5
27
  ## [0.12.1]
6
28
 
7
29
  - Clear timestamp stored in `session[:invisible_captcha_timestamp]` (#50)
@@ -97,6 +119,11 @@ All notable changes to this project will be documented in this file.
97
119
 
98
120
  - First version of controller filters
99
121
 
122
+ [1.1.0]: https://github.com/markets/invisible_captcha/compare/v1.0.1...v1.1.0
123
+ [1.0.1]: https://github.com/markets/invisible_captcha/compare/v1.0.0...v1.0.1
124
+ [1.0.0]: https://github.com/markets/invisible_captcha/compare/v0.13.0...v1.0.0
125
+ [0.13.0]: https://github.com/markets/invisible_captcha/compare/v0.12.2...v0.13.0
126
+ [0.12.2]: https://github.com/markets/invisible_captcha/compare/v0.12.1...v0.12.2
100
127
  [0.12.1]: https://github.com/markets/invisible_captcha/compare/v0.12.0...v0.12.1
101
128
  [0.12.0]: https://github.com/markets/invisible_captcha/compare/v0.11.0...v0.12.0
102
129
  [0.11.0]: https://github.com/markets/invisible_captcha/compare/v0.10.0...v0.11.0
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2017 Marc Anguera Insa
1
+ Copyright 2012-2019 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
@@ -7,19 +7,19 @@
7
7
 
8
8
  Invisible Captcha provides different techniques to protect your application against spambots.
9
9
 
10
- The main protection is a solution based on the `honeypot` principle, which provides a better user experience, since there is no extra steps for real users, but for the bots.
10
+ The main protection is a solution based on the `honeypot` principle, which provides a better user experience since there are no extra steps for real users, only for the bots.
11
11
 
12
12
  Essentially, the strategy consists on adding an input field :honey_pot: into the form that:
13
13
 
14
14
  - shouldn't be visible by the real users
15
15
  - should be left empty by the real users
16
- - will most be filled by spam bots
16
+ - will most likely be filled by spam bots
17
17
 
18
18
  It also comes with a time-sensitive :hourglass: form submission.
19
19
 
20
20
  ## Installation
21
21
 
22
- Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 2.2`.
22
+ Invisible Captcha is tested against Rails `>= 4.2` and Ruby `>= 2.3`.
23
23
 
24
24
  Add this line to your Gemfile and then execute `bundle install`:
25
25
 
@@ -47,7 +47,7 @@ class TopicsController < ApplicationController
47
47
  end
48
48
  ```
49
49
 
50
- This method will act as a `before_action` 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:
50
+ This method will act as a `before_action` 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 can define your own callback by passing a method to the `on_spam` option:
51
51
 
52
52
  ```ruby
53
53
  class TopicsController < ApplicationController
@@ -61,7 +61,7 @@ class TopicsController < ApplicationController
61
61
  end
62
62
  ```
63
63
 
64
- Note that is not 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:
64
+ Note that it is not mandatory to specify a `honeypot` attribute (neither 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:
65
65
 
66
66
  ```erb
67
67
  <%= form_tag(new_contact_path) do |f| %>
@@ -69,12 +69,32 @@ Note that is not mandatory to specify a `honeypot` attribute (nor in the view, n
69
69
  <% end %>
70
70
  ```
71
71
 
72
- In you controller:
72
+ In your controller:
73
73
 
74
74
  ```
75
75
  invisible_captcha only: [:new_contact]
76
76
  ```
77
77
 
78
+ `invisible_captcha` sends all messages to `flash[:error]`. For messages to appear on your pages, add `<%= flash[:error] %>` to `app/views/layouts/application.html.erb` (somewhere near the top of your `<body>` element):
79
+
80
+ ```erb
81
+ <!DOCTYPE html>
82
+ <html>
83
+ <head>
84
+ <title>Yet another Rails app</title>
85
+ <%= stylesheet_link_tag "application", media: "all" %>
86
+ <%= javascript_include_tag "application" %>
87
+ <%= csrf_meta_tags %>
88
+ </head>
89
+ <body>
90
+ <%= flash[:error] %>
91
+ <%= yield %>
92
+ </body>
93
+ </html>
94
+ ```
95
+
96
+ You can place `<%= flash[:error] %>` next to `:alert` and `:notice` message types, if you have them in your `app/views/layouts/application.html.erb`.
97
+
78
98
  ## Options and customization
79
99
 
80
100
  This section contains a description of all plugin options and customizations.
@@ -84,7 +104,7 @@ This section contains a description of all plugin options and customizations.
84
104
  You can customize:
85
105
 
86
106
  - `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).
87
- - `honeypots`: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name. By default, a random collection is already generated.
107
+ - `honeypots`: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name. By default, a random collection is already generated. As the random collection is stored in memory, it will not work if are running multiple Rails instances behind a load balancer. See [Multiple Rails instances](#multiple-rails-instances).
88
108
  - `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
89
109
  - `timestamp_threshold`: fastest time (in seconds) to expect a human to submit the form (see [original article by Yoav Aner](https://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"`).
90
110
  - `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
@@ -107,6 +127,20 @@ InvisibleCaptcha.setup do |config|
107
127
  end
108
128
  ```
109
129
 
130
+ #### Multiple Rails instances
131
+
132
+ If you have multiple Rails instances running behind a load balancer, you have to share the same honeypots collection between the instances.
133
+
134
+ Either use a fixed collection or share them between the instances using `Rails.cache`:
135
+
136
+ ```ruby
137
+ InvisibleCaptcha.setup do |config|
138
+ config.honeypots = Rails.cache.fetch('invisible_captcha_honeypots') do
139
+ (1..20).map { InvisibleCaptcha.generate_random_honeypot }
140
+ end
141
+ end
142
+ ```
143
+
110
144
  ### Controller method options:
111
145
 
112
146
  The `invisible_captcha` method accepts some options:
@@ -119,6 +153,7 @@ The `invisible_captcha` method accepts some options:
119
153
  - `timestamp_enabled`: enable/disable this technique at action level.
120
154
  - `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]`.
121
155
  - `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.
156
+ - `prepend`: the spam detection will run in a `prepend_before_action` if `prepend: true`, otherwise will run in a `before_action`.
122
157
 
123
158
  ### View helpers options:
124
159
 
@@ -145,6 +180,73 @@ You can also pass html options to the input:
145
180
  <%= invisible_captcha :subtitle, :topic, id: "your_id", class: "your_class" %>
146
181
  ```
147
182
 
183
+ ### Spam detection notifications
184
+
185
+ In addition to the `on_spam` controller callback, you can use the [Active Support Instrumentation API](https://guides.rubyonrails.org/active_support_instrumentation.html) to set up a global event handler that fires whenever spam is detected. This is useful for advanced logging, background processing, etc.
186
+
187
+ To set up a global event handler, [subscribe](https://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event) to the `invisible_captcha.spam_detected` event in an initializer:
188
+
189
+ ```ruby
190
+ # config/initializers/invisible_captcha.rb
191
+
192
+ ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
193
+ AwesomeLogger.warn(data[:message], data) # Log to an external logging service.
194
+ SpamRequest.create(data) # Record the blocked request in your database.
195
+ end
196
+ ```
197
+
198
+ The `data` passed to the subscriber is hash containing information about the request that was detected as spam. For example:
199
+
200
+ ```ruby
201
+ {
202
+ message: "Invisible Captcha honeypot param 'subtitle' was present.",
203
+ remote_ip: '127.0.0.1',
204
+ user_agent: 'Chrome 77',
205
+ controller: 'users',
206
+ action: 'create',
207
+ url: 'http://example.com/users',
208
+ params: {
209
+ topic: { subtitle: 'foo' },
210
+ controller: 'users',
211
+ action: 'create'
212
+ }
213
+ }
214
+ ```
215
+
216
+ _**Note:** `params` will be filtered according to your `Rails.application.config.filter_parameters` configuration, making them (probably) safe for logging. But always double-check that you're not inadvertently logging sensitive form data, like passwords and credit cards._
217
+
218
+ ### Content Security Policy
219
+
220
+ If you're using a Content Security Policy (CSP) in your Rails app, you will need to generate a nonce on the server, and pass `nonce: true` attribute to the view helper. Uncomment the following lines in your `config/initializers/content_security_policy.rb` file:
221
+
222
+ ```ruby
223
+ # Be sure to restart your server when you modify this file.
224
+
225
+ # If you are using UJS then enable automatic nonce generation
226
+ Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
227
+
228
+ # Set the nonce only to specific directives
229
+ Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
230
+ ```
231
+ Note that if you are already generating nonce for scripts, you'd have to include `script-src` to `content_security_policy_nonce_directives` as well:
232
+
233
+ ```ruby
234
+ Rails.application.config.content_security_policy_nonce_directives = %w(script-src style-src)
235
+ ```
236
+
237
+ And in your view helper, you need to pass `nonce: true` to the `invisible_captcha` helper:
238
+
239
+ ```erb
240
+ <%= invisible_captcha nonce: true %>
241
+ ```
242
+
243
+ **WARNING:** Content Security Policy can break your site! If you already run a website with third-party scripts, styles, images, and fonts, it is highly recommended to enable CSP in report-only mode and observe warnings as they appear. Learn more at MDN:
244
+
245
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
246
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
247
+
248
+ Note that Content Security Policy only works on Rails 5.2 and up.
249
+
148
250
  ### I18n
149
251
 
150
252
  `invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
@@ -156,17 +258,20 @@ en:
156
258
  timestamp_error_message: "Sorry, that was too quick! Please resubmit."
157
259
  ```
158
260
 
159
- You can override the english ones in your own i18n config files as well as add new ones for other locales.
261
+ You can override the English ones in your i18n config files as well as add new ones for other locales.
160
262
 
161
263
  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.
162
264
 
163
265
  ## Testing your controllers
164
266
 
165
- If you're encountering unexpected behaviour while testing controllers that use the `invisible_captcha` action filter, you may want to disable timestamp check for the test environment:
267
+ If you're encountering unexpected behaviour while testing controllers that use the `invisible_captcha` action filter, you may want to disable timestamp check for the test environment. Add the following snippet to the `config/initializers/invisible_captcha.rb` file:
166
268
 
167
269
  ```ruby
168
- # test/test_helper.rb, spec/rails_helper.rb, ...
169
- InvisibleCaptcha.timestamp_enabled = false
270
+ # Be sure to restart your server when you modify this file.
271
+
272
+ InvisibleCaptcha.setup do |config|
273
+ config.timestamp_enabled = !Rails.env.test?
274
+ end
170
275
  ```
171
276
 
172
277
  Another option is to wait for the timestamp check to be valid:
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require 'rspec/core/rake_task'
3
5
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "6.0.0.rc1"
5
+ gem "rails", "~> 6.0.0"
6
6
 
7
7
  gemspec path: "../"
@@ -15,11 +15,8 @@ Gem::Specification.new do |spec|
15
15
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
16
  spec.require_paths = ["lib"]
17
17
 
18
- spec.add_dependency 'rails', '>= 3.2.0'
18
+ spec.add_dependency 'rails', '>= 4.2'
19
19
 
20
20
  spec.add_development_dependency 'rspec-rails', '~> 3.1'
21
21
  spec.add_development_dependency 'appraisal'
22
- spec.add_development_dependency 'test-unit', '~> 3.0'
23
- spec.add_development_dependency 'byebug'
24
22
  end
25
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'invisible_captcha/version'
2
4
  require 'invisible_captcha/controller_ext'
3
5
  require 'invisible_captcha/view_helpers'
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ControllerExt
3
5
  module ClassMethods
4
6
  def invisible_captcha(options = {})
5
- if respond_to?(:before_action)
6
- before_action(options) do
7
+ if options.key?(:prepend)
8
+ prepend_before_action(options) do
7
9
  detect_spam(options)
8
10
  end
9
11
  else
10
- before_filter(options) do
12
+ before_action(options) do
11
13
  detect_spam(options)
12
14
  end
13
15
  end
@@ -22,8 +24,6 @@ module InvisibleCaptcha
22
24
  elsif honeypot_spam?(options)
23
25
  on_spam(options)
24
26
  end
25
-
26
- clear_session
27
27
  end
28
28
 
29
29
  def on_timestamp_spam(options = {})
@@ -55,28 +55,24 @@ module InvisibleCaptcha
55
55
 
56
56
  return false unless enabled
57
57
 
58
- timestamp = session[:invisible_captcha_timestamp]
58
+ @invisible_captcha_timestamp ||= session.delete(:invisible_captcha_timestamp)
59
59
 
60
60
  # Consider as spam if timestamp not in session, cause that means the form was not fetched at all
61
- unless timestamp
62
- warn("Invisible Captcha timestamp not found in session.")
61
+ unless @invisible_captcha_timestamp
62
+ warn_spam("Invisible Captcha timestamp not found in session.")
63
63
  return true
64
64
  end
65
65
 
66
- time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
66
+ time_to_submit = Time.zone.now - DateTime.iso8601(@invisible_captcha_timestamp)
67
67
  threshold = options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold
68
68
 
69
69
  # Consider as spam if form submitted too quickly
70
70
  if time_to_submit < threshold
71
- warn("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
71
+ warn_spam("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
72
72
  return true
73
73
  end
74
74
 
75
- false
76
- end
77
-
78
- def clear_session
79
- session.delete(:invisible_captcha_timestamp) if session[:invisible_captcha_timestamp]
75
+ return false
80
76
  end
81
77
 
82
78
  def honeypot_spam?(options = {})
@@ -88,7 +84,7 @@ module InvisibleCaptcha
88
84
  # - honeypot: params[:subtitle]
89
85
  # - honeypot with scope: params[:topic][:subtitle]
90
86
  if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
91
- warn("Invisible Captcha honeypot param '#{honeypot}' was present.")
87
+ warn_spam("Invisible Captcha honeypot param '#{honeypot}' was present.")
92
88
  return true
93
89
  else
94
90
  # No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
@@ -98,7 +94,7 @@ module InvisibleCaptcha
98
94
  else
99
95
  InvisibleCaptcha.honeypots.each do |default_honeypot|
100
96
  if params[default_honeypot].present?
101
- warn("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
97
+ warn_spam("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
102
98
  return true
103
99
  end
104
100
  end
@@ -107,8 +103,19 @@ module InvisibleCaptcha
107
103
  false
108
104
  end
109
105
 
110
- def warn(message)
106
+ def warn_spam(message)
111
107
  logger.warn("Potential spam detected for IP #{request.remote_ip}. #{message}")
108
+
109
+ ActiveSupport::Notifications.instrument(
110
+ 'invisible_captcha.spam_detected',
111
+ message: message,
112
+ remote_ip: request.remote_ip,
113
+ user_agent: request.user_agent,
114
+ controller: params[:controller],
115
+ action: params[:action],
116
+ url: request.url,
117
+ params: request.filtered_parameters
118
+ )
112
119
  end
113
120
  end
114
121
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module FormHelpers
3
5
  def invisible_captcha(honeypot, options = {})
4
6
  @template.invisible_captcha(honeypot, self.object_name, options)
5
7
  end
6
8
  end
7
- end
9
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  class Railtie < Rails::Railtie
3
5
  initializer 'invisible_captcha.rails_integration' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
- VERSION = "0.12.1"
4
+ VERSION = "1.1.0"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ViewHelpers
3
5
  # Builds the honeypot html
@@ -54,7 +56,13 @@ module InvisibleCaptcha
54
56
 
55
57
  return if visible
56
58
 
57
- content_tag(:style, media: 'screen') do
59
+ nonce = if Rails.version >= '5.2'
60
+ content_security_policy_nonce if options[:nonce]
61
+ else
62
+ nil
63
+ end
64
+
65
+ content_tag(:style, media: 'screen', nonce: nonce) do
58
66
  ".#{css_class} {#{InvisibleCaptcha.css_strategy}}"
59
67
  end
60
68
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
2
4
  render_views
3
5
 
4
6
  def switchable_post(action, params = {})
5
- if ::Rails::VERSION::STRING > '5'
7
+ if Rails.version > '5'
6
8
  post action, params: params
7
9
  else
8
10
  post action, params
@@ -10,7 +12,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
10
12
  end
11
13
 
12
14
  def switchable_put(action, params = {})
13
- if ::Rails::VERSION::STRING > '5'
15
+ if Rails.version > '5'
14
16
  put action, params: params
15
17
  else
16
18
  put action, params
@@ -63,12 +65,25 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
63
65
  expect(session[:invisible_captcha_timestamp]).to be_nil
64
66
  end
65
67
 
66
- it 'allow a custom on_timestamp_spam callback' do
68
+ it 'allows a custom on_timestamp_spam callback' do
67
69
  switchable_put :update, id: 1, topic: { title: 'bar' }
68
70
 
69
71
  expect(response.status).to eq(204)
70
72
  end
71
73
 
74
+ it 'allows a new timestamp to be set in the on_timestamp_spam callback' do
75
+ @controller.singleton_class.class_eval do
76
+ def custom_timestamp_callback
77
+ session[:invisible_captcha_timestamp] = 2.seconds.from_now(Time.zone.now).iso8601
78
+ head(204)
79
+ end
80
+ end
81
+
82
+ expect { switchable_put :update, id: 1, topic: { title: 'bar' } }
83
+ .to change { session[:invisible_captcha_timestamp] }
84
+ .to be_present
85
+ end
86
+
72
87
  context 'successful submissions' do
73
88
  it 'passes if submission on or after timestamp_threshold' do
74
89
  sleep InvisibleCaptcha.timestamp_threshold
@@ -128,5 +143,43 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
128
143
  expect(flash[:error]).not_to be_present
129
144
  expect(@controller.params[:topic].key?(:subtitle)).to eq(false)
130
145
  end
146
+
147
+ describe 'ActiveSupport::Notifications' do
148
+ let(:dummy_handler) { double(handle_event: nil) }
149
+
150
+ let!(:subscriber) do
151
+ subscriber = ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
152
+ dummy_handler.handle_event(data)
153
+ end
154
+
155
+ subscriber
156
+ end
157
+
158
+ after { ActiveSupport::Notifications.unsubscribe(subscriber) }
159
+
160
+ it 'dispatches an `invisible_captcha.spam_detected` event' do
161
+ # Skip the `with` matcher for Rails < 5 due to issues comparing arguments passed to / recived by the dummy event handler.
162
+ # https://github.com/markets/invisible_captcha/pull/62#issuecomment-552218501
163
+ if Rails.version > '5'
164
+ expect(dummy_handler).to receive(:handle_event).once.with(
165
+ message: "Invisible Captcha honeypot param 'subtitle' was present.",
166
+ remote_ip: '0.0.0.0',
167
+ user_agent: 'Rails Testing',
168
+ controller: 'topics',
169
+ action: 'create',
170
+ url: 'http://test.host/topics',
171
+ params: {
172
+ topic: { subtitle: "foo"},
173
+ controller: 'topics',
174
+ action: 'create'
175
+ }
176
+ )
177
+ else
178
+ expect(dummy_handler).to receive(:handle_event).once
179
+ end
180
+
181
+ switchable_post :create, topic: { subtitle: 'foo' }
182
+ end
183
+ end
131
184
  end
132
185
  end
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
@@ -39,4 +39,4 @@ Dummy::Application.configure do
39
39
 
40
40
  # Print deprecation notices to the stderr.
41
41
  config.active_support.deprecation = :stderr
42
- end
42
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha do
2
4
  it 'initialize with defaults' do
3
5
  InvisibleCaptcha.init!
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['RAILS_ENV'] = 'test'
2
4
 
3
5
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
@@ -5,6 +7,9 @@ require 'rspec/rails'
5
7
  require 'invisible_captcha'
6
8
 
7
9
  RSpec.configure do |config|
10
+ if Rails.version >= '5.2'
11
+ config.include ActionDispatch::ContentSecurityPolicy::Request, type: :helper
12
+ end
8
13
  config.disable_monkey_patching!
9
14
  config.order = :random
10
15
  config.expect_with :rspec
@@ -1,8 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
2
4
  before(:each) do
3
5
  allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
4
6
  allow(InvisibleCaptcha).to receive(:css_strategy).and_return("display:none;")
5
7
 
8
+ if Rails.version >= '5.2'
9
+ allow_any_instance_of(ActionDispatch::ContentSecurityPolicy::Request).to receive(:content_security_policy_nonce).and_return('123')
10
+ end
11
+
6
12
  # to test content_for and provide
7
13
  @view_flow = ActionView::OutputFlow.new
8
14
 
@@ -26,6 +32,12 @@ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
26
32
  expect(invisible_captcha(:subtitle, :topic, { class: 'foo_class' })).to match(/class="foo_class"/)
27
33
  end
28
34
 
35
+ if Rails.version >= '5.2'
36
+ it 'with CSP nonce' do
37
+ expect(invisible_captcha(:subtitle, :topic, { nonce: true })).to match(/nonce="123"/)
38
+ end
39
+ end
40
+
29
41
  it 'generated html + styles' do
30
42
  InvisibleCaptcha.honeypots = [:foo_id]
31
43
  output = invisible_captcha.gsub("\"", "'")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invisible_captcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Anguera Insa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-27 00:00:00.000000000 Z
11
+ date: 2020-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.2.0
19
+ version: '4.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.2.0
26
+ version: '4.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec-rails
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: test-unit
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: byebug
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
55
  description: Unobtrusive, flexible and simple spam protection for Rails applications
84
56
  using honeypot strategy for better user experience.
85
57
  email:
@@ -97,7 +69,6 @@ files:
97
69
  - LICENSE
98
70
  - README.md
99
71
  - Rakefile
100
- - gemfiles/rails_3.2.gemfile
101
72
  - gemfiles/rails_4.2.gemfile
102
73
  - gemfiles/rails_5.0.gemfile
103
74
  - gemfiles/rails_5.1.gemfile
@@ -113,6 +84,7 @@ files:
113
84
  - spec/controllers_spec.rb
114
85
  - spec/dummy/README.md
115
86
  - spec/dummy/Rakefile
87
+ - spec/dummy/app/assets/config/manifest.js
116
88
  - spec/dummy/app/assets/javascripts/application.js
117
89
  - spec/dummy/app/assets/stylesheets/application.css
118
90
  - spec/dummy/app/controllers/application_controller.rb
@@ -180,6 +152,7 @@ test_files:
180
152
  - spec/controllers_spec.rb
181
153
  - spec/dummy/README.md
182
154
  - spec/dummy/Rakefile
155
+ - spec/dummy/app/assets/config/manifest.js
183
156
  - spec/dummy/app/assets/javascripts/application.js
184
157
  - spec/dummy/app/assets/stylesheets/application.css
185
158
  - spec/dummy/app/controllers/application_controller.rb
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 3.2.0"
6
-
7
- gemspec path: "../"