invisible_captcha 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,39 +3,25 @@
3
3
  RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
4
4
  render_views
5
5
 
6
- def switchable_post(action, params = {})
7
- if Rails.version > '5'
8
- post action, params: params
9
- else
10
- post action, params
11
- end
12
- end
13
-
14
- def switchable_put(action, params = {})
15
- if Rails.version > '5'
16
- put action, params: params
17
- else
18
- put action, params
19
- end
20
- end
21
-
22
6
  before(:each) do
23
7
  @controller = TopicsController.new
24
8
  request.env['HTTP_REFERER'] = 'http://test.host/topics'
9
+
25
10
  InvisibleCaptcha.init!
26
11
  InvisibleCaptcha.timestamp_threshold = 1
12
+ InvisibleCaptcha.spinner_enabled = false
27
13
  end
28
14
 
29
15
  context 'without invisible_captcha_timestamp in session' do
30
16
  it 'fails like if it was submitted too fast' do
31
- switchable_post :create, topic: { title: 'foo' }
17
+ post :create, params: { topic: { title: 'foo' } }
32
18
 
33
19
  expect(response).to redirect_to 'http://test.host/topics'
34
20
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
35
21
  end
36
22
 
37
23
  it 'passes if disabled at action level' do
38
- switchable_post :copy, topic: { title: 'foo' }
24
+ post :copy, params: { topic: { title: 'foo' } }
39
25
 
40
26
  expect(flash[:error]).not_to be_present
41
27
  expect(response.body).to be_present
@@ -43,7 +29,8 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
43
29
 
44
30
  it 'passes if disabled at app level' do
45
31
  InvisibleCaptcha.timestamp_enabled = false
46
- switchable_post :create, topic: { title: 'foo' }
32
+
33
+ post :create, params: { topic: { title: 'foo' } }
47
34
 
48
35
  expect(flash[:error]).not_to be_present
49
36
  expect(response.body).to be_present
@@ -56,7 +43,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
56
43
  end
57
44
 
58
45
  it 'fails if submission before timestamp_threshold' do
59
- switchable_post :create, topic: { title: 'foo' }
46
+ post :create, params: { topic: { title: 'foo' } }
60
47
 
61
48
  expect(response).to redirect_to 'http://test.host/topics'
62
49
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
@@ -66,7 +53,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
66
53
  end
67
54
 
68
55
  it 'allows a custom on_timestamp_spam callback' do
69
- switchable_put :update, id: 1, topic: { title: 'bar' }
56
+ put :update, params: { id: 1, topic: { title: 'bar' } }
70
57
 
71
58
  expect(response.status).to eq(204)
72
59
  end
@@ -79,7 +66,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
79
66
  end
80
67
  end
81
68
 
82
- expect { switchable_put :update, id: 1, topic: { title: 'bar' } }
69
+ expect { put :update, params: { id: 1, topic: { title: 'bar' } } }
83
70
  .to change { session[:invisible_captcha_timestamp] }
84
71
  .to be_present
85
72
  end
@@ -88,10 +75,12 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
88
75
  it 'passes if submission on or after timestamp_threshold' do
89
76
  sleep InvisibleCaptcha.timestamp_threshold
90
77
 
91
- switchable_post :create, topic: {
92
- title: 'foobar',
93
- author: 'author',
94
- body: 'body that passes validation'
78
+ post :create, params: {
79
+ topic: {
80
+ title: 'foobar',
81
+ author: 'author',
82
+ body: 'body that passes validation'
83
+ }
95
84
  }
96
85
 
97
86
  expect(flash[:error]).not_to be_present
@@ -104,7 +93,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
104
93
  it 'allow to set a custom timestamp_threshold per action' do
105
94
  sleep 2 # custom threshold
106
95
 
107
- switchable_post :publish, id: 1
96
+ post :publish, params: { id: 1 }
108
97
 
109
98
  expect(flash[:error]).not_to be_present
110
99
  expect(response.body).to be_present
@@ -115,30 +104,75 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
115
104
  context 'honeypot attribute' do
116
105
  before(:each) do
117
106
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
107
+
118
108
  # Wait for valid submission
119
109
  sleep InvisibleCaptcha.timestamp_threshold
120
110
  end
121
111
 
122
112
  it 'fails with spam' do
123
- switchable_post :create, topic: { subtitle: 'foo' }
113
+ post :create, params: { topic: { subtitle: 'foo' } }
124
114
 
125
115
  expect(response.body).to be_blank
126
116
  end
127
117
 
128
118
  it 'passes with no spam' do
129
- switchable_post :create, topic: { title: 'foo' }
119
+ post :create, params: { topic: { title: 'foo' } }
130
120
 
131
121
  expect(response.body).to be_present
132
122
  end
133
123
 
124
+ context 'with random honeypot' do
125
+ context 'auto-scoped' do
126
+ it 'passes with no spam' do
127
+ post :categorize, params: { topic: { title: 'foo' } }
128
+
129
+ expect(response.body).to be_present
130
+ end
131
+
132
+ it 'fails with spam' do
133
+ post :categorize, params: { topic: { "#{InvisibleCaptcha.honeypots.sample}": 'foo' } }
134
+
135
+ expect(response.body).to be_blank
136
+ end
137
+ end
138
+
139
+ context 'with no scope' do
140
+ it 'passes with no spam' do
141
+ post :categorize
142
+
143
+ expect(response.body).to be_present
144
+ end
145
+
146
+ it 'fails with spam' do
147
+ post :categorize, params: { "#{InvisibleCaptcha.honeypots.sample}": 'foo' }
148
+
149
+ expect(response.body).to be_blank
150
+ end
151
+ end
152
+
153
+ context 'with scope' do
154
+ it 'fails with spam' do
155
+ post :rename, params: { topic: { "#{InvisibleCaptcha.honeypots.sample}": 'foo' } }
156
+
157
+ expect(response.body).to be_blank
158
+ end
159
+
160
+ it 'passes with no spam' do
161
+ post :rename, params: { topic: { title: 'foo' } }
162
+
163
+ expect(response.body).to be_blank
164
+ end
165
+ end
166
+ end
167
+
134
168
  it 'allow a custom on_spam callback' do
135
- switchable_put :update, id: 1, topic: { subtitle: 'foo' }
169
+ put :update, params: { id: 1, topic: { subtitle: 'foo' } }
136
170
 
137
171
  expect(response.body).to redirect_to(new_topic_path)
138
172
  end
139
173
 
140
174
  it 'honeypot is removed from params if you use a custom honeypot' do
141
- switchable_post :create, topic: { title: 'foo', subtitle: '' }
175
+ post :create, params: { topic: { title: 'foo', subtitle: '' } }
142
176
 
143
177
  expect(flash[:error]).not_to be_present
144
178
  expect(@controller.params[:topic].key?(:subtitle)).to eq(false)
@@ -155,31 +189,49 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
155
189
  subscriber
156
190
  end
157
191
 
158
- after { ActiveSupport::Notifications.unsubscribe(subscriber) }
192
+ after { ActiveSupport::Notifications.unsubscribe(subscriber) }
159
193
 
160
194
  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',
195
+ expect(dummy_handler).to receive(:handle_event).once.with({
196
+ message: "[Invisible Captcha] Potential spam detected for IP 0.0.0.0. Honeypot param 'subtitle' was present.",
197
+ remote_ip: '0.0.0.0',
198
+ user_agent: 'Rails Testing',
199
+ controller: 'topics',
200
+ action: 'create',
201
+ url: 'http://test.host/topics',
202
+ params: {
203
+ topic: { subtitle: "foo"},
168
204
  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
205
+ action: 'create'
206
+ }
207
+ })
180
208
 
181
- switchable_post :create, topic: { subtitle: 'foo' }
209
+ post :create, params: { topic: { subtitle: 'foo' } }
182
210
  end
183
211
  end
184
212
  end
213
+
214
+ context 'spinner attribute' do
215
+ before(:each) do
216
+ InvisibleCaptcha.spinner_enabled = true
217
+ InvisibleCaptcha.secret = 'secret'
218
+ session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
219
+ session[:invisible_captcha_spinner] = '32ab649161f9f6faeeb323746de1a25d'
220
+
221
+ # Wait for valid submission
222
+ sleep InvisibleCaptcha.timestamp_threshold
223
+ end
224
+
225
+ it 'fails with no spam, but mismatch of spinner' do
226
+ post :create, params: { topic: { title: 'foo' }, spinner: 'mismatch' }
227
+
228
+ expect(response.body).to be_blank
229
+ end
230
+
231
+ it 'passes with no spam and spinner match' do
232
+ post :create, params: { topic: { title: 'foo' }, spinner: '32ab649161f9f6faeeb323746de1a25d' }
233
+
234
+ expect(response.body).to be_present
235
+ end
236
+ end
185
237
  end
@@ -9,6 +9,10 @@ class TopicsController < ApplicationController
9
9
 
10
10
  invisible_captcha honeypot: :subtitle, only: :copy, timestamp_enabled: false
11
11
 
12
+ invisible_captcha scope: :topic, only: :rename
13
+
14
+ invisible_captcha only: :categorize
15
+
12
16
  def index
13
17
  redirect_to new_topic_path
14
18
  end
@@ -28,6 +32,14 @@ class TopicsController < ApplicationController
28
32
  end
29
33
 
30
34
  def update
35
+ redirect_to new_topic_path
36
+ end
37
+
38
+ def rename
39
+ end
40
+
41
+ def categorize
42
+ redirect_to new_topic_path
31
43
  end
32
44
 
33
45
  def publish
@@ -2,8 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Dummy</title>
5
- <%= stylesheet_link_tag "application", :media => "all" %>
6
- <%= javascript_include_tag "application" %>
5
+ <%= stylesheet_link_tag "/styles.css" %>
7
6
  <%= csrf_meta_tags %>
8
7
  <%= invisible_captcha_styles %>
9
8
  </head>
@@ -2,9 +2,7 @@ require File.expand_path('../boot', __FILE__)
2
2
 
3
3
  require 'action_controller/railtie'
4
4
  require 'action_view/railtie'
5
- require 'action_mailer/railtie'
6
5
  require 'active_model/railtie'
7
- require 'sprockets/railtie'
8
6
 
9
7
  # Require the gems listed in Gemfile, including any gems
10
8
  # you've limited to :test, :development, or :production.
@@ -14,7 +14,7 @@ Dummy::Application.configure do
14
14
  config.action_controller.perform_caching = false
15
15
 
16
16
  # Don't care if the mailer can't send.
17
- config.action_mailer.raise_delivery_errors = false
17
+ # config.action_mailer.raise_delivery_errors = false
18
18
 
19
19
  # Print deprecation notices to the Rails logger.
20
20
  config.active_support.deprecation = :log
@@ -31,9 +31,6 @@ Dummy::Application.configure do
31
31
  # yet still be able to expire them through the digest params.
32
32
  # config.assets.digest = true
33
33
 
34
- # quiet assets
35
- config.assets.quiet = true
36
-
37
34
  # Adds additional error checking when serving assets at runtime.
38
35
  # Checks for improperly declared sprockets dependencies.
39
36
  # Raises helpful error messages.
@@ -14,13 +14,8 @@ Dummy::Application.configure do
14
14
 
15
15
  # Disable serving static files from the `/public` folder by default since
16
16
  # Apache or NGINX already handles this.
17
- if Rails.version >= "5.0.0"
18
- config.public_file_server.enabled = true
19
- config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
20
- else
21
- config.serve_static_files = true
22
- config.static_cache_control = "public, max-age=3600"
23
- end
17
+ config.public_file_server.enabled = true
18
+ config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
24
19
 
25
20
  # Show full error reports and disable caching.
26
21
  config.consider_all_requests_local = true
@@ -35,7 +30,7 @@ Dummy::Application.configure do
35
30
  # Tell Action Mailer not to deliver emails to the real world.
36
31
  # The :test delivery method accumulates sent emails in the
37
32
  # ActionMailer::Base.deliveries array.
38
- config.action_mailer.delivery_method = :test
33
+ # config.action_mailer.delivery_method = :test
39
34
 
40
35
  # Print deprecation notices to the stderr.
41
36
  config.active_support.deprecation = :stderr
@@ -1,6 +1,8 @@
1
1
  Rails.application.routes.draw do
2
2
  resources :topics do
3
3
  post :publish, on: :member
4
+ post :rename, on: :collection
5
+ post :categorize, on: :collection
4
6
  post :copy, on: :collection
5
7
  end
6
8
 
@@ -8,8 +8,11 @@ h1 {
8
8
  border-bottom: 3px solid;
9
9
  }
10
10
 
11
- input,
12
- textarea {
11
+ a {
12
+ color: #000;
13
+ }
14
+
15
+ input, textarea {
13
16
  border: 0;
14
17
  margin-bottom: 1.5em;
15
18
  }
@@ -19,7 +22,9 @@ input {
19
22
  }
20
23
 
21
24
  button {
22
- background-color: #f0f0f0;
25
+ color: #fff;
26
+ background-color: #000;
27
+ border: none;
23
28
  border-radius: 0.25em;
24
29
  height: 3em;
25
30
  width: 10em;
@@ -28,4 +33,4 @@ button {
28
33
 
29
34
  .errors {
30
35
  color: darkred;
31
- }
36
+ }
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,19 @@
2
2
 
3
3
  ENV['RAILS_ENV'] = 'test'
4
4
 
5
+ require 'simplecov'
6
+ if ENV['CI']
7
+ require 'simplecov-cobertura'
8
+ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
9
+ end
10
+ SimpleCov.start
11
+
5
12
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
6
13
  require 'rspec/rails'
7
14
  require 'invisible_captcha'
8
15
 
9
16
  RSpec.configure do |config|
10
- if Rails.version >= '5.2'
11
- config.include ActionDispatch::ContentSecurityPolicy::Request, type: :helper
12
- end
17
+ config.include ActionDispatch::ContentSecurityPolicy::Request, type: :helper
13
18
  config.disable_monkey_patching!
14
19
  config.order = :random
15
20
  config.expect_with :rspec
@@ -17,17 +22,3 @@ RSpec.configure do |config|
17
22
  mocks.verify_partial_doubles = true
18
23
  end
19
24
  end
20
-
21
- # Rails 4.2 call `initialize` inside `recycle!`. However Ruby 2.6 doesn't allow calling `initialize` twice.
22
- # More info: https://github.com/rails/rails/issues/34790
23
- if RUBY_VERSION >= "2.6.0" && Rails.version < "5"
24
- module ActionController
25
- class TestResponse < ActionDispatch::TestResponse
26
- def recycle!
27
- @mon_mutex_owner_object_id = nil
28
- @mon_mutex = nil
29
- initialize
30
- end
31
- end
32
- end
33
- end
@@ -2,12 +2,8 @@
2
2
 
3
3
  RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
4
4
  before(:each) do
5
- allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
6
5
  allow(InvisibleCaptcha).to receive(:css_strategy).and_return("display:none;")
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
6
+ allow_any_instance_of(ActionDispatch::ContentSecurityPolicy::Request).to receive(:content_security_policy_nonce).and_return('123')
11
7
 
12
8
  # to test content_for and provide
13
9
  @view_flow = ActionView::OutputFlow.new
@@ -32,10 +28,8 @@ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
32
28
  expect(invisible_captcha(:subtitle, :topic, { class: 'foo_class' })).to match(/class="foo_class"/)
33
29
  end
34
30
 
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
31
+ it 'with CSP nonce' do
32
+ expect(invisible_captcha(:subtitle, :topic, { nonce: true })).to match(/nonce="123"/)
39
33
  end
40
34
 
41
35
  it 'generated html + styles' do
@@ -64,6 +58,18 @@ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
64
58
  end
65
59
  end
66
60
 
61
+ context "should have spinner field" do
62
+ it 'that exists by default, spinner_enabled is true' do
63
+ InvisibleCaptcha.spinner_enabled = true
64
+ expect(invisible_captcha).to match(/spinner/)
65
+ end
66
+
67
+ it 'that does not exist if spinner_enabled is false' do
68
+ InvisibleCaptcha.spinner_enabled = false
69
+ expect(invisible_captcha).not_to match(/spinner/)
70
+ end
71
+ end
72
+
67
73
  it 'should set spam timestamp' do
68
74
  invisible_captcha
69
75
  expect(session[:invisible_captcha_timestamp]).to eq(Time.zone.now.iso8601)