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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 619741f1ea048808a9235e1bc02a814c2d6606aed599e188c3ea99d464e2fc85
4
- data.tar.gz: e309645da54a5ac5c898b83f8e90b5132361001724469ceabe18bf7f3195fbac
3
+ metadata.gz: f0cec3797da20a8ab16dd95113f15c271acb6764478291372e14fc8d3556e0a3
4
+ data.tar.gz: 960f481d33667095c861f91665786ab6e5a60c5a162e9f90fea709b99821b91e
5
5
  SHA512:
6
- metadata.gz: 9e8328c22c89ee565ef34c2cff0e2ecad4aae2b9e71a9627bb6162e57e1013577fa53f65a56a217928e8d0503017b4c2508cec435ecf0c911eb257da53087f59
7
- data.tar.gz: 49fa2e0f20b2489eb082c9bd4cd3fe1a09f4d4ddd03022676e12a91fc7779a0bab36e1a628205a0a1f29904da8292a984ef11a317bda486eed0c74f1a5d42909
6
+ metadata.gz: 846a83081e7139d5c6ba56674d3f5506abebc583c9f2fc62501fa8dc30d230f391f3a61a63a8a231456368a09c8ba20635d8af1176356d7574c97d1c4c0a9f9c
7
+ data.tar.gz: fab5e0b02f702ee3f71bb2914d766a6a3ba1fedc90232189844497e5c818927b7cd31ff17eeb0fbf3583494b4e3e54c14e00511055df6a604608a5d850e3c0ad
@@ -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 = 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
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
- private :spamtrap_valid_nonce?, :spamtrap_remap_params, :spamtrap_remap_hash
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
 
@@ -1,5 +1,5 @@
1
1
  module Spamtrap
2
2
 
3
- VERSION = '0.3.1'
3
+ VERSION = '0.3.2'
4
4
 
5
5
  end
data/lib/spamtrap.rb CHANGED
@@ -2,7 +2,8 @@ require 'openssl'
2
2
 
3
3
  module Spamtrap
4
4
  class << self
5
- attr_writer :nonce, :nonce_timeout, :mutate
5
+ attr_accessor :on_trap
6
+ attr_writer :nonce, :nonce_timeout, :mutate
6
7
 
7
8
  def nonce
8
9
  @nonce || false
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spamtrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cedric Howe