spamtrap 0.3.1 → 0.3.2
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.
- checksums.yaml +4 -4
- data/lib/spamtrap/controller.rb +15 -4
- data/lib/spamtrap/version.rb +1 -1
- data/lib/spamtrap.rb +2 -1
- data/test/controller_test.rb +126 -0
- data/test/test_helper.rb +3 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0cec3797da20a8ab16dd95113f15c271acb6764478291372e14fc8d3556e0a3
|
|
4
|
+
data.tar.gz: 960f481d33667095c861f91665786ab6e5a60c5a162e9f90fea709b99821b91e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 846a83081e7139d5c6ba56674d3f5506abebc583c9f2fc62501fa8dc30d230f391f3a61a63a8a231456368a09c8ba20635d8af1176356d7574c97d1c4c0a9f9c
|
|
7
|
+
data.tar.gz: fab5e0b02f702ee3f71bb2914d766a6a3ba1fedc90232189844497e5c818927b7cd31ff17eeb0fbf3583494b4e3e54c14e00511055df6a604608a5d850e3c0ad
|
data/lib/spamtrap/controller.rb
CHANGED
|
@@ -9,9 +9,10 @@ module Spamtrap::Controller
|
|
|
9
9
|
def spamtrap(honeypot = 'spamtrap', options = {}, &block)
|
|
10
10
|
# Capture explicit per-call values; use sentinel so globals are read
|
|
11
11
|
# at request time rather than at class definition time.
|
|
12
|
-
nonce_opt
|
|
13
|
-
timeout_opt
|
|
14
|
-
mutate_opt
|
|
12
|
+
nonce_opt = options.key?(:nonce) ? options.delete(:nonce) : :global
|
|
13
|
+
timeout_opt = options.key?(:nonce_timeout) ? options.delete(:nonce_timeout) : :global
|
|
14
|
+
mutate_opt = options.key?(:mutate) ? options.delete(:mutate) : :global
|
|
15
|
+
on_trap_opt = options.key?(:on_trap) ? options.delete(:on_trap) : :global
|
|
15
16
|
|
|
16
17
|
before_action(options) do |controller|
|
|
17
18
|
controller.instance_eval(&block) if block_given?
|
|
@@ -24,9 +25,11 @@ module Spamtrap::Controller
|
|
|
24
25
|
|
|
25
26
|
if params[honeypot].present?
|
|
26
27
|
Rails.logger.warn "Spamtrap triggered by #{request.remote_ip}."
|
|
28
|
+
spamtrap_invoke_on_trap(:honeypot, on_trap_opt)
|
|
27
29
|
head 200
|
|
28
30
|
elsif nonce_enabled && !spamtrap_valid_nonce?(nonce_timeout)
|
|
29
31
|
Rails.logger.warn "Spamtrap nonce invalid from #{request.remote_ip}."
|
|
32
|
+
spamtrap_invoke_on_trap(:nonce, on_trap_opt)
|
|
30
33
|
head 200
|
|
31
34
|
end
|
|
32
35
|
end
|
|
@@ -69,7 +72,15 @@ module Spamtrap::Controller
|
|
|
69
72
|
end
|
|
70
73
|
end
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
def spamtrap_invoke_on_trap(reason, on_trap_opt)
|
|
76
|
+
callback = on_trap_opt == :global ? Spamtrap.on_trap : on_trap_opt
|
|
77
|
+
return unless callback.respond_to?(:call)
|
|
78
|
+
callback.call(reason: reason, request: request)
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
Rails.logger.error "Spamtrap on_trap callback raised: #{e.class}: #{e.message}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private :spamtrap_valid_nonce?, :spamtrap_remap_params, :spamtrap_remap_hash, :spamtrap_invoke_on_trap
|
|
73
84
|
|
|
74
85
|
end
|
|
75
86
|
|
data/lib/spamtrap/version.rb
CHANGED
data/lib/spamtrap.rb
CHANGED
data/test/controller_test.rb
CHANGED
|
@@ -345,3 +345,129 @@ class GlobalOverrideControllerTest < ActionController::TestCase
|
|
|
345
345
|
assert_equal 'body', response.body
|
|
346
346
|
end
|
|
347
347
|
end
|
|
348
|
+
|
|
349
|
+
# Controllers for on_trap callback tests.
|
|
350
|
+
class OnTrapGlobalCallbackController < ActionController::Base
|
|
351
|
+
spamtrap :trap_field, only: :create
|
|
352
|
+
|
|
353
|
+
def create
|
|
354
|
+
render plain: 'success'
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
class OnTrapGlobalNonceCallbackController < ActionController::Base
|
|
359
|
+
spamtrap :trap_field, nonce: true, only: :create
|
|
360
|
+
|
|
361
|
+
def create
|
|
362
|
+
render plain: 'success'
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
class OnTrapPerDeclarationController < ActionController::Base
|
|
367
|
+
spamtrap :trap_field, only: :create,
|
|
368
|
+
on_trap: ->(reason:, request:) { Thread.current[:per_decl_calls] << { reason: reason, ip: request.remote_ip } }
|
|
369
|
+
|
|
370
|
+
def create
|
|
371
|
+
render plain: 'success'
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
class OnTrapCallbackTest < ActionController::TestCase
|
|
376
|
+
tests OnTrapGlobalCallbackController
|
|
377
|
+
|
|
378
|
+
setup do
|
|
379
|
+
@calls = []
|
|
380
|
+
Spamtrap.on_trap = ->(reason:, request:) { @calls << { reason: reason, ip: request.remote_ip } }
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
teardown do
|
|
384
|
+
Spamtrap.on_trap = nil
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def test_global_callback_invoked_on_honeypot_trap
|
|
388
|
+
post :create, params: { trap_field: 'spam' }
|
|
389
|
+
assert_response :ok
|
|
390
|
+
assert_empty response.body
|
|
391
|
+
assert_equal 1, @calls.size
|
|
392
|
+
assert_equal :honeypot, @calls.first[:reason]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def test_global_callback_not_invoked_on_legitimate_request
|
|
396
|
+
post :create, params: { trap_field: '' }
|
|
397
|
+
assert_response :ok
|
|
398
|
+
assert_equal 'success', response.body
|
|
399
|
+
assert_empty @calls
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def test_no_callback_when_on_trap_is_nil
|
|
403
|
+
Spamtrap.on_trap = nil
|
|
404
|
+
post :create, params: { trap_field: 'spam' }
|
|
405
|
+
assert_response :ok
|
|
406
|
+
assert_empty response.body
|
|
407
|
+
# no error raised — test simply passes
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
class OnTrapNonceCallbackTest < ActionController::TestCase
|
|
412
|
+
include NonceTestHelper
|
|
413
|
+
tests OnTrapGlobalNonceCallbackController
|
|
414
|
+
|
|
415
|
+
setup do
|
|
416
|
+
@calls = []
|
|
417
|
+
Spamtrap.on_trap = ->(reason:, request:) { @calls << { reason: reason, ip: request.remote_ip } }
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
teardown do
|
|
421
|
+
Spamtrap.on_trap = nil
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def test_global_callback_invoked_on_nonce_trap
|
|
425
|
+
post :create, params: { trap_field: '' }
|
|
426
|
+
assert_response :ok
|
|
427
|
+
assert_empty response.body
|
|
428
|
+
assert_equal 1, @calls.size
|
|
429
|
+
assert_equal :nonce, @calls.first[:reason]
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
class OnTrapPerDeclarationCallbackTest < ActionController::TestCase
|
|
434
|
+
tests OnTrapPerDeclarationController
|
|
435
|
+
|
|
436
|
+
setup do
|
|
437
|
+
Thread.current[:per_decl_calls] = []
|
|
438
|
+
@global_calls = []
|
|
439
|
+
Spamtrap.on_trap = ->(reason:, request:) { @global_calls << reason }
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
teardown do
|
|
443
|
+
Spamtrap.on_trap = nil
|
|
444
|
+
Thread.current[:per_decl_calls] = nil
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def test_per_declaration_callback_takes_precedence_over_global
|
|
448
|
+
post :create, params: { trap_field: 'spam' }
|
|
449
|
+
assert_response :ok
|
|
450
|
+
assert_empty response.body
|
|
451
|
+
assert_equal 1, Thread.current[:per_decl_calls].size
|
|
452
|
+
assert_equal :honeypot, Thread.current[:per_decl_calls].first[:reason]
|
|
453
|
+
assert_empty @global_calls
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
class OnTrapCallbackErrorResilienceTest < ActionController::TestCase
|
|
458
|
+
tests OnTrapGlobalCallbackController
|
|
459
|
+
|
|
460
|
+
setup do
|
|
461
|
+
Spamtrap.on_trap = ->(**) { raise 'callback exploded' }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
teardown do
|
|
465
|
+
Spamtrap.on_trap = nil
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def test_broken_callback_does_not_prevent_head_200
|
|
469
|
+
post :create, params: { trap_field: 'spam' }
|
|
470
|
+
assert_response :ok
|
|
471
|
+
assert_empty response.body
|
|
472
|
+
end
|
|
473
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -21,6 +21,9 @@ Rails.application.routes.draw do
|
|
|
21
21
|
post 'nested_mutation/create', to: 'nested_mutation#create'
|
|
22
22
|
post 'global_defaults/create', to: 'global_defaults#create'
|
|
23
23
|
post 'global_override/create', to: 'global_override#create'
|
|
24
|
+
post 'on_trap_global_callback/create', to: 'on_trap_global_callback#create'
|
|
25
|
+
post 'on_trap_global_nonce_callback/create', to: 'on_trap_global_nonce_callback#create'
|
|
26
|
+
post 'on_trap_per_declaration/create', to: 'on_trap_per_declaration#create'
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
class ActionController::TestCase
|