spamtrap 0.3.5 → 0.4.3

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.
data/lib/spamtrap.rb CHANGED
@@ -1,9 +1,18 @@
1
1
  require 'openssl'
2
+ require 'ipaddr'
2
3
 
3
4
  module Spamtrap
4
5
  class << self
5
6
  attr_accessor :on_trap
6
- attr_writer :nonce, :nonce_timeout, :mutate
7
+ attr_writer :stable_ids
8
+
9
+ # Whether mutated fields keep an id/for derived from the real field name (e.g.
10
+ # #message_name) instead of the opaque encrypted one. Default true.
11
+ def stable_ids
12
+ @stable_ids.nil? ? true : @stable_ids
13
+ end
14
+
15
+ attr_writer :nonce, :nonce_timeout, :mutate, :mutation_timeout, :nonce_skew, :allowed_params
7
16
 
8
17
  def nonce
9
18
  @nonce || false
@@ -13,12 +22,176 @@ module Spamtrap
13
22
  @nonce_timeout || 1800
14
23
  end
15
24
 
25
+ # false, true (strict), :strict, or :lenient (remap only; never traps a plaintext key).
16
26
  def mutate
17
27
  @mutate || false
18
28
  end
29
+
30
+ # Extra top-level param names a strict action accepts unencrypted (e.g. app-added form fields).
31
+ def allowed_params
32
+ Array(@allowed_params).map(&:to_s)
33
+ end
34
+
35
+ def mutation_timeout
36
+ @mutation_timeout || nonce_timeout
37
+ end
38
+
39
+ # How far a token's timestamp may sit in the future (clock skew), before it's rejected.
40
+ def nonce_skew
41
+ @nonce_skew || 60
42
+ end
43
+
44
+ attr_writer :nonce_store
45
+
46
+ # Where nonce: :single_use records seen nonce ids; defaults to the app's cache.
47
+ def nonce_store
48
+ @nonce_store || Rails.cache
49
+ end
50
+
51
+ attr_writer :trap_response
52
+
53
+ # How a trapped request responds: :head, :no_content, :unprocessable, :redirect_back,
54
+ # a callable, or a Hash keyed by trap reason. See Spamtrap::Controller#spamtrap_render_trap.
55
+ def trap_response
56
+ @trap_response || :head
57
+ end
58
+
59
+ attr_writer :enabled
60
+
61
+ # Global kill switch; false skips every spamtrap check (honeypot, nonce, mutation).
62
+ def enabled
63
+ @enabled.nil? ? true : @enabled
64
+ end
65
+
66
+ attr_writer :filter_parameters
67
+
68
+ # Apply the app's filter_parameters to mutated field names in the log. Default true.
69
+ def filter_parameters
70
+ @filter_parameters.nil? ? true : @filter_parameters
71
+ end
72
+
73
+ attr_writer :nonce_bind_ip
74
+
75
+ # true binds the nonce to the full client IP (default), :prefix to its /24 or /48
76
+ # network (tolerates mobile handoff/CGNAT), false does not bind it to the IP at all.
77
+ def nonce_bind_ip
78
+ @nonce_bind_ip.nil? ? true : @nonce_bind_ip
79
+ end
80
+
81
+ attr_writer :min_fill_time
82
+
83
+ # Minimum seconds between render and submission a human needs; false or 0 disables. Default 1.
84
+ def min_fill_time
85
+ @min_fill_time.nil? ? 1 : @min_fill_time
86
+ end
87
+
88
+ attr_writer :secret_key_base
89
+
90
+ # The current HKDF source secret; defaults to the app's own so no setup is needed to opt in.
91
+ def secret_key_base
92
+ @secret_key_base || Rails.application.secret_key_base
93
+ end
94
+
95
+ attr_writer :honeypot_styles, :js_proof, :suspicious_if
96
+
97
+ # Decoys f.spamtrap renders: any of :textarea, :text, :checkbox. Default is the textarea only.
98
+ def honeypot_styles
99
+ Array(@honeypot_styles.nil? ? :textarea : @honeypot_styles).map(&:to_sym)
100
+ end
101
+
102
+ # Require a hidden field that only an executing script fills in; excludes no-JS users.
103
+ def js_proof
104
+ @js_proof || false
105
+ end
106
+
107
+ # App-defined heuristic run after every other check; truthy result traps as :content.
108
+ def suspicious_if
109
+ @suspicious_if
110
+ end
111
+
112
+ attr_writer :previous_secret_key_base
113
+
114
+ # Set during a rotation window so tokens minted under the old secret still verify/decrypt.
115
+ def previous_secret_key_base
116
+ @previous_secret_key_base
117
+ end
118
+
119
+ attr_writer :token_context
120
+
121
+ # Callable taking the request and returning a String mixed into every token, so a token
122
+ # minted on one hostname doesn't verify on another. Off (nil) by default.
123
+ def token_context
124
+ @token_context
125
+ end
126
+ end
127
+
128
+ # nil when token_context is unset, so the token format is unchanged for apps that don't opt in.
129
+ def self.token_context_for(request)
130
+ token_context && token_context.call(request).to_s
131
+ end
132
+
133
+ # Normalises ip per mode: true keeps it as-is, :prefix masks to its /24 (IPv4) or /48 (IPv6)
134
+ # network (IPv4-mapped addresses unmapped first so they mask as IPv4), false discards it.
135
+ def self.normalize_ip(ip, mode = nonce_bind_ip)
136
+ case mode
137
+ when false
138
+ ''
139
+ when :prefix
140
+ begin
141
+ addr = IPAddr.new(ip.to_s)
142
+ addr = addr.native if addr.ipv4_mapped?
143
+ bits = addr.ipv4? ? 24 : 48
144
+ "#{addr.mask(bits)}/#{bits}"
145
+ rescue IPAddr::InvalidAddressError
146
+ ip.to_s
147
+ end
148
+ else
149
+ ip.to_s
150
+ end
151
+ end
152
+
153
+ # Parameter names for each honeypot style, derived from the declared name so the view and
154
+ # controller agree without extra configuration. With mutation on they are encrypted like any field.
155
+ def self.honeypot_fields(name)
156
+ { textarea: name.to_s, text: "#{name}_input", checkbox: "#{name}_check" }
157
+ end
158
+
159
+ # Key for Rack::Attack / Rails' rate_limit, scoped by the normalized client IP.
160
+ def self.throttle_key(request)
161
+ "spamtrap:#{normalize_ip(request.remote_ip)}"
162
+ end
163
+
164
+ # Wires the gem into Action Controller and Action View. The Railtie calls the two halves
165
+ # from on_load hooks; outside a Rails app call install! after those frameworks are loaded.
166
+ def self.install!
167
+ install_controller!
168
+ install_form_builder!
169
+ end
170
+
171
+ def self.install_controller!
172
+ ActionController::Base.include Spamtrap::Controller unless ActionController::Base < Spamtrap::Controller
173
+ # const_defined?(false) avoids autoloading action_controller/api.rb for apps that never use it.
174
+ return unless ActionController.const_defined?(:API, false)
175
+
176
+ ActionController::API.include Spamtrap::Controller unless ActionController::API < Spamtrap::Controller
177
+ end
178
+
179
+ def self.install_form_builder!
180
+ builder = ActionView::Helpers::FormBuilder
181
+ builder.prepend Spamtrap::FormBuilderMutation unless builder < Spamtrap::FormBuilderMutation
182
+ builder.include Spamtrap::FormBuilderHelper unless builder < Spamtrap::FormBuilderHelper
183
+ end
184
+
185
+ # True once action_view/helpers has loaded, checked without triggering the autoload
186
+ # (defined?(ActionView::Helpers::FormBuilder) would load it).
187
+ def self.form_builder_loaded?
188
+ defined?(ActionView) && ActionView.autoload?(:Helpers).nil? &&
189
+ ActionView.const_defined?(:Helpers, false) && ActionView::Helpers.const_defined?(:FormBuilder, false)
19
190
  end
20
191
 
21
192
  require 'spamtrap/crypto'
22
193
  require 'spamtrap/controller'
23
194
  require 'spamtrap/helper'
195
+ require 'spamtrap/parameter_filter'
196
+ require 'spamtrap/railtie' if defined?(Rails::Railtie)
24
197
  end
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.5
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cedric Howe
@@ -10,19 +10,107 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rails
13
+ name: actionpack
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.0'
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '7.0'
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: actionview
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.2'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9'
52
+ - !ruby/object:Gem::Dependency
53
+ name: railties
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '7.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '7.2'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '9'
72
+ - !ruby/object:Gem::Dependency
73
+ name: rake
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ - !ruby/object:Gem::Dependency
87
+ name: minitest
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: activemodel
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
26
114
  description: Create bogus form fields (honeypots) that will be filled-in by spambots.
27
115
  When submitted, the form data will be discarded while still returning a 200 response.
28
116
  email: cedric@freezerbox.com
@@ -30,15 +118,17 @@ executables: []
30
118
  extensions: []
31
119
  extra_rdoc_files: []
32
120
  files:
121
+ - lib/generators/spamtrap/install/install_generator.rb
122
+ - lib/generators/spamtrap/install/templates/spamtrap.rb
33
123
  - lib/spamtrap.rb
34
124
  - lib/spamtrap/controller.rb
35
125
  - lib/spamtrap/crypto.rb
36
126
  - lib/spamtrap/helper.rb
127
+ - lib/spamtrap/parameter_filter.rb
128
+ - lib/spamtrap/railtie.rb
129
+ - lib/spamtrap/test_helper.rb
37
130
  - lib/spamtrap/version.rb
38
- - test/controller_test.rb
39
- - test/form_builder_mutation_test.rb
40
- - test/test_helper.rb
41
- homepage: http://github.com/cedric/spamtrap/
131
+ homepage: https://github.com/cedric/spamtrap/
42
132
  licenses:
43
133
  - MIT
44
134
  metadata: {}
@@ -49,17 +139,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
139
  requirements:
50
140
  - - ">="
51
141
  - !ruby/object:Gem::Version
52
- version: 3.0.0
142
+ version: 3.1.0
53
143
  required_rubygems_version: !ruby/object:Gem::Requirement
54
144
  requirements:
55
145
  - - ">="
56
146
  - !ruby/object:Gem::Version
57
147
  version: 1.3.6
58
148
  requirements: []
59
- rubygems_version: 4.0.6
149
+ rubygems_version: 4.0.21
60
150
  specification_version: 4
61
151
  summary: Simple spamtrap for spambots.
62
- test_files:
63
- - test/controller_test.rb
64
- - test/form_builder_mutation_test.rb
65
- - test/test_helper.rb
152
+ test_files: []