durable_parameters 0.2.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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +853 -0
  4. data/Rakefile +29 -0
  5. data/app/params/account_params.rb.example +38 -0
  6. data/app/params/application_params.rb +16 -0
  7. data/lib/durable_parameters/adapters/hanami.rb +138 -0
  8. data/lib/durable_parameters/adapters/rage.rb +124 -0
  9. data/lib/durable_parameters/adapters/rails.rb +280 -0
  10. data/lib/durable_parameters/adapters/sinatra.rb +91 -0
  11. data/lib/durable_parameters/core/application_params.rb +334 -0
  12. data/lib/durable_parameters/core/configuration.rb +83 -0
  13. data/lib/durable_parameters/core/forbidden_attributes_protection.rb +48 -0
  14. data/lib/durable_parameters/core/parameters.rb +643 -0
  15. data/lib/durable_parameters/core/params_registry.rb +110 -0
  16. data/lib/durable_parameters/core.rb +15 -0
  17. data/lib/durable_parameters/log_subscriber.rb +34 -0
  18. data/lib/durable_parameters/railtie.rb +65 -0
  19. data/lib/durable_parameters/version.rb +7 -0
  20. data/lib/durable_parameters.rb +41 -0
  21. data/lib/generators/rails/USAGE +12 -0
  22. data/lib/generators/rails/durable_parameters_controller_generator.rb +17 -0
  23. data/lib/generators/rails/templates/controller.rb +94 -0
  24. data/lib/legacy/action_controller/application_params.rb +235 -0
  25. data/lib/legacy/action_controller/parameters.rb +524 -0
  26. data/lib/legacy/action_controller/params_registry.rb +108 -0
  27. data/lib/legacy/active_model/forbidden_attributes_protection.rb +40 -0
  28. data/test/action_controller_required_params_test.rb +36 -0
  29. data/test/action_controller_tainted_params_test.rb +29 -0
  30. data/test/active_model_mass_assignment_taint_protection_test.rb +25 -0
  31. data/test/application_params_array_test.rb +245 -0
  32. data/test/application_params_edge_cases_test.rb +361 -0
  33. data/test/application_params_test.rb +893 -0
  34. data/test/controller_generator_test.rb +31 -0
  35. data/test/core_parameters_test.rb +2376 -0
  36. data/test/durable_parameters_test.rb +115 -0
  37. data/test/enhanced_error_messages_test.rb +120 -0
  38. data/test/gemfiles/Gemfile.rails-3.0.x +14 -0
  39. data/test/gemfiles/Gemfile.rails-3.1.x +14 -0
  40. data/test/gemfiles/Gemfile.rails-3.2.x +14 -0
  41. data/test/log_on_unpermitted_params_test.rb +49 -0
  42. data/test/metadata_validation_test.rb +294 -0
  43. data/test/multi_parameter_attributes_test.rb +38 -0
  44. data/test/parameters_core_methods_test.rb +503 -0
  45. data/test/parameters_integration_test.rb +553 -0
  46. data/test/parameters_permit_test.rb +491 -0
  47. data/test/parameters_require_test.rb +9 -0
  48. data/test/parameters_taint_test.rb +98 -0
  49. data/test/params_registry_concurrency_test.rb +422 -0
  50. data/test/params_registry_test.rb +112 -0
  51. data/test/permit_by_model_test.rb +227 -0
  52. data/test/raise_on_unpermitted_params_test.rb +32 -0
  53. data/test/test_helper.rb +38 -0
  54. data/test/transform_params_edge_cases_test.rb +526 -0
  55. data/test/transformation_test.rb +360 -0
  56. metadata +223 -0
@@ -0,0 +1,360 @@
1
+ require 'test_helper'
2
+
3
+ class TransformationTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+ end
7
+
8
+ def teardown
9
+ ActionController::ParamsRegistry.clear!
10
+ end
11
+
12
+ # Test basic transformation
13
+ def test_basic_transformation
14
+ params_class = Class.new(ActionController::ApplicationParams) do
15
+ def self.name
16
+ 'UserParams'
17
+ end
18
+
19
+ allow :email
20
+ allow :name
21
+
22
+ transform :email do |value, metadata|
23
+ value&.downcase&.strip
24
+ end
25
+ end
26
+
27
+ ActionController::ParamsRegistry.register(:user, params_class)
28
+
29
+ params = ActionController::Parameters.new(
30
+ user: {
31
+ email: ' TEST@EXAMPLE.COM ',
32
+ name: 'John Doe'
33
+ }
34
+ )
35
+
36
+ result = params.require(:user).transform_params
37
+
38
+ assert_equal 'test@example.com', result[:email]
39
+ assert_equal 'John Doe', result[:name]
40
+ end
41
+
42
+ # Test transformation with metadata
43
+ def test_transformation_with_metadata
44
+ params_class = Class.new(ActionController::ApplicationParams) do
45
+ def self.name
46
+ 'PostParams'
47
+ end
48
+
49
+ allow :title
50
+ allow :published
51
+
52
+ metadata :current_user
53
+
54
+ transform :published do |value, metadata|
55
+ # Only admins can publish
56
+ metadata[:current_user]&.fetch(:admin, false) ? value : false
57
+ end
58
+ end
59
+
60
+ ActionController::ParamsRegistry.register(:post, params_class)
61
+
62
+ # Test with admin user
63
+ admin_user = { admin: true }
64
+ params1 = ActionController::Parameters.new(
65
+ post: {
66
+ title: 'My Post',
67
+ published: true
68
+ }
69
+ )
70
+
71
+ result1 = params1.require(:post).transform_params(current_user: admin_user)
72
+ assert_equal true, result1[:published]
73
+
74
+ # Test with non-admin user
75
+ regular_user = { admin: false }
76
+ params2 = ActionController::Parameters.new(
77
+ post: {
78
+ title: 'My Post',
79
+ published: true
80
+ }
81
+ )
82
+
83
+ result2 = params2.require(:post).transform_params(current_user: regular_user)
84
+ assert_equal false, result2[:published]
85
+ end
86
+
87
+ # Test multiple transformations
88
+ def test_multiple_transformations
89
+ params_class = Class.new(ActionController::ApplicationParams) do
90
+ def self.name
91
+ 'AccountParams'
92
+ end
93
+
94
+ allow :email
95
+ allow :username
96
+ allow :bio
97
+
98
+ transform :email do |value, metadata|
99
+ value&.downcase&.strip
100
+ end
101
+
102
+ transform :username do |value, metadata|
103
+ value&.strip&.gsub(/\s+/, '_')
104
+ end
105
+
106
+ transform :bio do |value, metadata|
107
+ value&.strip&.slice(0, 200)
108
+ end
109
+ end
110
+
111
+ ActionController::ParamsRegistry.register(:account, params_class)
112
+
113
+ params = ActionController::Parameters.new(
114
+ account: {
115
+ email: ' Admin@Example.COM ',
116
+ username: ' john doe ',
117
+ bio: 'A' * 300
118
+ }
119
+ )
120
+
121
+ result = params.require(:account).transform_params
122
+
123
+ assert_equal 'admin@example.com', result[:email]
124
+ assert_equal 'john_doe', result[:username] # gsub(/\s+/, '_') replaces consecutive spaces with single underscore
125
+ assert_equal 200, result[:bio].length
126
+ end
127
+
128
+ # Test transformation with nil value
129
+ def test_transformation_with_nil_value
130
+ params_class = Class.new(ActionController::ApplicationParams) do
131
+ def self.name
132
+ 'UserParams'
133
+ end
134
+
135
+ allow :email
136
+
137
+ transform :email do |value, metadata|
138
+ value&.downcase
139
+ end
140
+ end
141
+
142
+ ActionController::ParamsRegistry.register(:user, params_class)
143
+
144
+ params = ActionController::Parameters.new(
145
+ user: {
146
+ email: nil
147
+ }
148
+ )
149
+
150
+ result = params.require(:user).transform_params
151
+
152
+ assert_nil result[:email]
153
+ end
154
+
155
+ # Test transformation doesn't affect denied attributes
156
+ def test_transformation_ignores_denied_attributes
157
+ params_class = Class.new(ActionController::ApplicationParams) do
158
+ def self.name
159
+ 'UserParams'
160
+ end
161
+
162
+ allow :email
163
+ deny :role
164
+
165
+ transform :email do |value, metadata|
166
+ value&.downcase
167
+ end
168
+
169
+ transform :role do |value, metadata|
170
+ 'admin' # This should never be applied since role is denied
171
+ end
172
+ end
173
+
174
+ ActionController::ParamsRegistry.register(:user, params_class)
175
+
176
+ params = ActionController::Parameters.new(
177
+ user: {
178
+ email: 'TEST@EXAMPLE.COM',
179
+ role: 'user'
180
+ }
181
+ )
182
+
183
+ result = params.require(:user).transform_params
184
+
185
+ assert_equal 'test@example.com', result[:email]
186
+ assert_nil result[:role] # Denied attributes are filtered out
187
+ end
188
+
189
+ # Test transformation with action-specific metadata
190
+ def test_transformation_with_action_metadata
191
+ params_class = Class.new(ActionController::ApplicationParams) do
192
+ def self.name
193
+ 'ArticleParams'
194
+ end
195
+
196
+ allow :slug
197
+
198
+ transform :slug do |value, metadata|
199
+ if metadata[:action] == :create
200
+ # Auto-generate slug on create
201
+ value || "article-#{Time.now.to_i}"
202
+ else
203
+ # Keep existing slug on update
204
+ value
205
+ end
206
+ end
207
+ end
208
+
209
+ ActionController::ParamsRegistry.register(:article, params_class)
210
+
211
+ # Create action with no slug
212
+ params1 = ActionController::Parameters.new(article: { slug: nil })
213
+ result1 = params1.require(:article).transform_params(action: :create)
214
+ assert_match(/^article-\d+$/, result1[:slug])
215
+
216
+ # Update action with slug
217
+ params2 = ActionController::Parameters.new(article: { slug: 'my-slug' })
218
+ result2 = params2.require(:article).transform_params(action: :update)
219
+ assert_equal 'my-slug', result2[:slug]
220
+ end
221
+
222
+ # Test transformation inheritance
223
+ def test_transformation_inheritance
224
+ base_params = Class.new(ActionController::ApplicationParams) do
225
+ def self.name
226
+ 'BaseParams'
227
+ end
228
+
229
+ allow :email
230
+
231
+ transform :email do |value, metadata|
232
+ value&.downcase
233
+ end
234
+ end
235
+
236
+ child_params = Class.new(base_params) do
237
+ def self.name
238
+ 'ChildParams'
239
+ end
240
+
241
+ allow :name
242
+
243
+ transform :name do |value, metadata|
244
+ value&.upcase
245
+ end
246
+ end
247
+
248
+ ActionController::ParamsRegistry.register(:child, child_params)
249
+
250
+ params = ActionController::Parameters.new(
251
+ child: {
252
+ email: 'TEST@EXAMPLE.COM',
253
+ name: 'john doe'
254
+ }
255
+ )
256
+
257
+ result = params.require(:child).transform_params
258
+
259
+ assert_equal 'test@example.com', result[:email] # From parent
260
+ assert_equal 'JOHN DOE', result[:name] # From child
261
+ end
262
+
263
+ # Test transformation without params class (should not raise)
264
+ def test_transformation_without_params_class
265
+ params = ActionController::Parameters.new(
266
+ user: {
267
+ email: 'TEST@EXAMPLE.COM'
268
+ }
269
+ )
270
+
271
+ result = params.require(:user).transform_params
272
+
273
+ # Should return empty permitted params (no transformation or filtering)
274
+ assert result.permitted?
275
+ assert_nil result[:email]
276
+ end
277
+
278
+ # Test transformation with complex metadata
279
+ def test_transformation_with_complex_metadata
280
+ params_class = Class.new(ActionController::ApplicationParams) do
281
+ def self.name
282
+ 'CommentParams'
283
+ end
284
+
285
+ allow :content
286
+ allow :author_name
287
+
288
+ metadata :current_user, :ip_address
289
+
290
+ transform :author_name do |value, metadata|
291
+ if metadata[:current_user]
292
+ metadata[:current_user][:name]
293
+ else
294
+ "Anonymous (#{metadata[:ip_address]})"
295
+ end
296
+ end
297
+ end
298
+
299
+ ActionController::ParamsRegistry.register(:comment, params_class)
300
+
301
+ # With current_user
302
+ params1 = ActionController::Parameters.new(
303
+ comment: {
304
+ content: 'Great post!',
305
+ author_name: 'ignored'
306
+ }
307
+ )
308
+
309
+ result1 = params1.require(:comment).transform_params(
310
+ current_user: { name: 'John Doe' },
311
+ ip_address: '192.168.1.1'
312
+ )
313
+
314
+ assert_equal 'John Doe', result1[:author_name]
315
+
316
+ # Without current_user
317
+ params2 = ActionController::Parameters.new(
318
+ comment: {
319
+ content: 'Great post!',
320
+ author_name: 'ignored'
321
+ }
322
+ )
323
+
324
+ result2 = params2.require(:comment).transform_params(
325
+ ip_address: '192.168.1.100'
326
+ )
327
+
328
+ assert_equal 'Anonymous (192.168.1.100)', result2[:author_name]
329
+ end
330
+
331
+ # Test that transformations are applied before filtering
332
+ def test_transformations_applied_before_filtering
333
+ params_class = Class.new(ActionController::ApplicationParams) do
334
+ def self.name
335
+ 'ProductParams'
336
+ end
337
+
338
+ allow :price
339
+
340
+ # Transform price from cents to dollars
341
+ transform :price do |value, metadata|
342
+ value.is_a?(Numeric) ? value / 100.0 : value
343
+ end
344
+ end
345
+
346
+ ActionController::ParamsRegistry.register(:product, params_class)
347
+
348
+ params = ActionController::Parameters.new(
349
+ product: {
350
+ price: 1999, # 19.99 in cents
351
+ other: 'ignored'
352
+ }
353
+ )
354
+
355
+ result = params.require(:product).transform_params
356
+
357
+ assert_equal 19.99, result[:price]
358
+ assert_nil result[:other] # Not allowed
359
+ end
360
+ end
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: durable_parameters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - David J Berube
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: activesupport
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">"
45
+ - !ruby/object:Gem::Version
46
+ version: '6.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">"
52
+ - !ruby/object:Gem::Version
53
+ version: '6.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: actionpack
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">"
59
+ - !ruby/object:Gem::Version
60
+ version: '6.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">"
66
+ - !ruby/object:Gem::Version
67
+ version: '6.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: activemodel
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">"
73
+ - !ruby/object:Gem::Version
74
+ version: '6.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">"
80
+ - !ruby/object:Gem::Version
81
+ version: '6.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: railties
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">"
87
+ - !ruby/object:Gem::Version
88
+ version: '6.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">"
94
+ - !ruby/object:Gem::Version
95
+ version: '6.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: sinatra
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '1.4'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '1.4'
110
+ description: Durable Parameters provides a whitelist-based approach to mass assignment
111
+ protection. This gem is framework-agnostic with adapters for Rails, Sinatra, Hanami,
112
+ and Rage.
113
+ email:
114
+ - djberube@durableprogramming.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - MIT-LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - app/params/account_params.rb.example
123
+ - app/params/application_params.rb
124
+ - lib/durable_parameters.rb
125
+ - lib/durable_parameters/adapters/hanami.rb
126
+ - lib/durable_parameters/adapters/rage.rb
127
+ - lib/durable_parameters/adapters/rails.rb
128
+ - lib/durable_parameters/adapters/sinatra.rb
129
+ - lib/durable_parameters/core.rb
130
+ - lib/durable_parameters/core/application_params.rb
131
+ - lib/durable_parameters/core/configuration.rb
132
+ - lib/durable_parameters/core/forbidden_attributes_protection.rb
133
+ - lib/durable_parameters/core/parameters.rb
134
+ - lib/durable_parameters/core/params_registry.rb
135
+ - lib/durable_parameters/log_subscriber.rb
136
+ - lib/durable_parameters/railtie.rb
137
+ - lib/durable_parameters/version.rb
138
+ - lib/generators/rails/USAGE
139
+ - lib/generators/rails/durable_parameters_controller_generator.rb
140
+ - lib/generators/rails/templates/controller.rb
141
+ - lib/legacy/action_controller/application_params.rb
142
+ - lib/legacy/action_controller/parameters.rb
143
+ - lib/legacy/action_controller/params_registry.rb
144
+ - lib/legacy/active_model/forbidden_attributes_protection.rb
145
+ - test/action_controller_required_params_test.rb
146
+ - test/action_controller_tainted_params_test.rb
147
+ - test/active_model_mass_assignment_taint_protection_test.rb
148
+ - test/application_params_array_test.rb
149
+ - test/application_params_edge_cases_test.rb
150
+ - test/application_params_test.rb
151
+ - test/controller_generator_test.rb
152
+ - test/core_parameters_test.rb
153
+ - test/durable_parameters_test.rb
154
+ - test/enhanced_error_messages_test.rb
155
+ - test/gemfiles/Gemfile.rails-3.0.x
156
+ - test/gemfiles/Gemfile.rails-3.1.x
157
+ - test/gemfiles/Gemfile.rails-3.2.x
158
+ - test/log_on_unpermitted_params_test.rb
159
+ - test/metadata_validation_test.rb
160
+ - test/multi_parameter_attributes_test.rb
161
+ - test/parameters_core_methods_test.rb
162
+ - test/parameters_integration_test.rb
163
+ - test/parameters_permit_test.rb
164
+ - test/parameters_require_test.rb
165
+ - test/parameters_taint_test.rb
166
+ - test/params_registry_concurrency_test.rb
167
+ - test/params_registry_test.rb
168
+ - test/permit_by_model_test.rb
169
+ - test/raise_on_unpermitted_params_test.rb
170
+ - test/test_helper.rb
171
+ - test/transform_params_edge_cases_test.rb
172
+ - test/transformation_test.rb
173
+ homepage: https://github.com/durableprogramming/durable_parameters
174
+ licenses:
175
+ - MIT
176
+ metadata: {}
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.7.2
192
+ specification_version: 4
193
+ summary: Framework-agnostic durable parameters with adapters for Rails, Sinatra, Hanami,
194
+ and Rage
195
+ test_files:
196
+ - test/action_controller_required_params_test.rb
197
+ - test/action_controller_tainted_params_test.rb
198
+ - test/active_model_mass_assignment_taint_protection_test.rb
199
+ - test/application_params_array_test.rb
200
+ - test/application_params_edge_cases_test.rb
201
+ - test/application_params_test.rb
202
+ - test/controller_generator_test.rb
203
+ - test/core_parameters_test.rb
204
+ - test/durable_parameters_test.rb
205
+ - test/enhanced_error_messages_test.rb
206
+ - test/gemfiles/Gemfile.rails-3.0.x
207
+ - test/gemfiles/Gemfile.rails-3.1.x
208
+ - test/gemfiles/Gemfile.rails-3.2.x
209
+ - test/log_on_unpermitted_params_test.rb
210
+ - test/metadata_validation_test.rb
211
+ - test/multi_parameter_attributes_test.rb
212
+ - test/parameters_core_methods_test.rb
213
+ - test/parameters_integration_test.rb
214
+ - test/parameters_permit_test.rb
215
+ - test/parameters_require_test.rb
216
+ - test/parameters_taint_test.rb
217
+ - test/params_registry_concurrency_test.rb
218
+ - test/params_registry_test.rb
219
+ - test/permit_by_model_test.rb
220
+ - test/raise_on_unpermitted_params_test.rb
221
+ - test/test_helper.rb
222
+ - test/transform_params_edge_cases_test.rb
223
+ - test/transformation_test.rb