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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +853 -0
- data/Rakefile +29 -0
- data/app/params/account_params.rb.example +38 -0
- data/app/params/application_params.rb +16 -0
- data/lib/durable_parameters/adapters/hanami.rb +138 -0
- data/lib/durable_parameters/adapters/rage.rb +124 -0
- data/lib/durable_parameters/adapters/rails.rb +280 -0
- data/lib/durable_parameters/adapters/sinatra.rb +91 -0
- data/lib/durable_parameters/core/application_params.rb +334 -0
- data/lib/durable_parameters/core/configuration.rb +83 -0
- data/lib/durable_parameters/core/forbidden_attributes_protection.rb +48 -0
- data/lib/durable_parameters/core/parameters.rb +643 -0
- data/lib/durable_parameters/core/params_registry.rb +110 -0
- data/lib/durable_parameters/core.rb +15 -0
- data/lib/durable_parameters/log_subscriber.rb +34 -0
- data/lib/durable_parameters/railtie.rb +65 -0
- data/lib/durable_parameters/version.rb +7 -0
- data/lib/durable_parameters.rb +41 -0
- data/lib/generators/rails/USAGE +12 -0
- data/lib/generators/rails/durable_parameters_controller_generator.rb +17 -0
- data/lib/generators/rails/templates/controller.rb +94 -0
- data/lib/legacy/action_controller/application_params.rb +235 -0
- data/lib/legacy/action_controller/parameters.rb +524 -0
- data/lib/legacy/action_controller/params_registry.rb +108 -0
- data/lib/legacy/active_model/forbidden_attributes_protection.rb +40 -0
- data/test/action_controller_required_params_test.rb +36 -0
- data/test/action_controller_tainted_params_test.rb +29 -0
- data/test/active_model_mass_assignment_taint_protection_test.rb +25 -0
- data/test/application_params_array_test.rb +245 -0
- data/test/application_params_edge_cases_test.rb +361 -0
- data/test/application_params_test.rb +893 -0
- data/test/controller_generator_test.rb +31 -0
- data/test/core_parameters_test.rb +2376 -0
- data/test/durable_parameters_test.rb +115 -0
- data/test/enhanced_error_messages_test.rb +120 -0
- data/test/gemfiles/Gemfile.rails-3.0.x +14 -0
- data/test/gemfiles/Gemfile.rails-3.1.x +14 -0
- data/test/gemfiles/Gemfile.rails-3.2.x +14 -0
- data/test/log_on_unpermitted_params_test.rb +49 -0
- data/test/metadata_validation_test.rb +294 -0
- data/test/multi_parameter_attributes_test.rb +38 -0
- data/test/parameters_core_methods_test.rb +503 -0
- data/test/parameters_integration_test.rb +553 -0
- data/test/parameters_permit_test.rb +491 -0
- data/test/parameters_require_test.rb +9 -0
- data/test/parameters_taint_test.rb +98 -0
- data/test/params_registry_concurrency_test.rb +422 -0
- data/test/params_registry_test.rb +112 -0
- data/test/permit_by_model_test.rb +227 -0
- data/test/raise_on_unpermitted_params_test.rb +32 -0
- data/test/test_helper.rb +38 -0
- data/test/transform_params_edge_cases_test.rb +526 -0
- data/test/transformation_test.rb +360 -0
- metadata +223 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sinatra/base'
|
|
4
|
+
require 'durable_parameters/core'
|
|
5
|
+
|
|
6
|
+
module StrongParameters
|
|
7
|
+
module Adapters
|
|
8
|
+
# Sinatra adapter for Strong Parameters
|
|
9
|
+
#
|
|
10
|
+
# This adapter integrates the core Strong Parameters functionality with
|
|
11
|
+
# Sinatra applications, providing a simple params wrapper and error handling.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage in a Sinatra app
|
|
14
|
+
# require 'sinatra/base'
|
|
15
|
+
# require 'durable_parameters/adapters/sinatra'
|
|
16
|
+
#
|
|
17
|
+
# class MyApp < Sinatra::Base
|
|
18
|
+
# register StrongParameters::Adapters::Sinatra
|
|
19
|
+
#
|
|
20
|
+
# post '/users' do
|
|
21
|
+
# user_params = strong_params.require(:user).permit(:name, :email)
|
|
22
|
+
# # ... use user_params
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
module Sinatra
|
|
26
|
+
# Sinatra-specific Parameters implementation
|
|
27
|
+
class Parameters < StrongParameters::Core::Parameters
|
|
28
|
+
# Sinatra typically uses string keys, so we normalize to strings
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def normalize_key(key)
|
|
32
|
+
key.to_s
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Module to register with Sinatra applications
|
|
37
|
+
module Helpers
|
|
38
|
+
# Access request parameters as a Strong Parameters object.
|
|
39
|
+
#
|
|
40
|
+
# @return [Parameters] the request parameters wrapped in a Parameters object
|
|
41
|
+
def strong_params
|
|
42
|
+
@_strong_params ||= ::StrongParameters::Adapters::Sinatra::Parameters.new(params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Alias for strong_params for Rails compatibility
|
|
46
|
+
alias sp strong_params
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Error handler for ParameterMissing
|
|
50
|
+
module ErrorHandlers
|
|
51
|
+
def self.registered(app)
|
|
52
|
+
app.error StrongParameters::Core::ParameterMissing do
|
|
53
|
+
halt 400, { error: "Required parameter missing: #{env['sinatra.error'].param}" }.to_json
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
app.error StrongParameters::Core::ForbiddenAttributes do
|
|
57
|
+
halt 400, { error: "Forbidden attributes in mass assignment" }.to_json
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
app.error StrongParameters::Core::UnpermittedParameters do
|
|
61
|
+
halt 400, { error: "Unpermitted parameters: #{env['sinatra.error'].params.join(', ')}" }.to_json
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Register the adapter with a Sinatra application
|
|
67
|
+
def self.registered(app)
|
|
68
|
+
app.helpers Helpers
|
|
69
|
+
app.register ErrorHandlers
|
|
70
|
+
|
|
71
|
+
# Configure logging for unpermitted parameters in development
|
|
72
|
+
if app.development?
|
|
73
|
+
::StrongParameters::Adapters::Sinatra::Parameters.action_on_unpermitted_parameters = :log
|
|
74
|
+
::StrongParameters::Adapters::Sinatra::Parameters.unpermitted_notification_handler = lambda do |keys|
|
|
75
|
+
app.logger.warn "Unpermitted parameters: #{keys.join(', ')}" if app.respond_to?(:logger)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Convenience method for class-level registration
|
|
81
|
+
def self.included(base)
|
|
82
|
+
base.register self if base.respond_to?(:register)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Register with Sinatra::Base if it's loaded
|
|
89
|
+
if defined?(::Sinatra::Base)
|
|
90
|
+
::Sinatra.register StrongParameters::Adapters::Sinatra
|
|
91
|
+
end
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module StrongParameters
|
|
6
|
+
module Core
|
|
7
|
+
# Base class for declarative parameter permission definitions.
|
|
8
|
+
#
|
|
9
|
+
# ApplicationParams provides a DSL for defining which attributes are allowed
|
|
10
|
+
# or denied for mass assignment. This enables a centralized, declarative
|
|
11
|
+
# approach to parameter filtering that's more maintainable than inline
|
|
12
|
+
# permit() calls.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# class UserParams < StrongParameters::Core::ApplicationParams
|
|
16
|
+
# allow :name
|
|
17
|
+
# allow :email
|
|
18
|
+
# deny :is_admin
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example With action-specific permissions
|
|
22
|
+
# class PostParams < StrongParameters::Core::ApplicationParams
|
|
23
|
+
# allow :title
|
|
24
|
+
# allow :body
|
|
25
|
+
# allow :published, only: :create
|
|
26
|
+
# allow :view_count, except: :create
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example With metadata declaration
|
|
30
|
+
# class AccountParams < StrongParameters::Core::ApplicationParams
|
|
31
|
+
# allow :name
|
|
32
|
+
# metadata :ip_address, :role
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# @example With transformations
|
|
36
|
+
# class UserParams < StrongParameters::Core::ApplicationParams
|
|
37
|
+
# allow :email
|
|
38
|
+
# allow :role
|
|
39
|
+
# metadata :current_user
|
|
40
|
+
#
|
|
41
|
+
# transform :email do |value, metadata|
|
|
42
|
+
# value&.downcase&.strip
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# transform :role do |value, metadata|
|
|
46
|
+
# metadata[:current_user]&.admin? ? value : 'user'
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
class ApplicationParams
|
|
50
|
+
class << self
|
|
51
|
+
# Returns the list of allowed attributes.
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Symbol>] array of allowed attribute names
|
|
54
|
+
def allowed_attributes
|
|
55
|
+
@allowed_attributes ||= []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the list of denied attributes.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Symbol>] array of denied attribute names
|
|
61
|
+
def denied_attributes
|
|
62
|
+
@denied_attributes ||= []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the flags hash.
|
|
66
|
+
#
|
|
67
|
+
# @return [Hash<Symbol, Object>] hash of flag names to values
|
|
68
|
+
def flags
|
|
69
|
+
@flags ||= {}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the set of allowed metadata keys.
|
|
73
|
+
#
|
|
74
|
+
# @return [Set<Symbol>] set of allowed metadata key names
|
|
75
|
+
# @note :current_user is always implicitly allowed
|
|
76
|
+
def allowed_metadata
|
|
77
|
+
@allowed_metadata ||= Set.new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the hash of transformations.
|
|
81
|
+
#
|
|
82
|
+
# @return [Hash<Symbol, Proc>] hash of attribute names to transformation procs
|
|
83
|
+
def transformations
|
|
84
|
+
@transformations ||= {}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# DSL method to allow an attribute
|
|
88
|
+
# @param attribute [Symbol, String, Hash] the attribute name to allow, or a hash for arrays
|
|
89
|
+
# @param options [Hash] additional options
|
|
90
|
+
# - :only - only allow this attribute for these actions
|
|
91
|
+
# - :except - allow this attribute except for these actions
|
|
92
|
+
# - :array - if true, permit an array of scalar values
|
|
93
|
+
# Examples:
|
|
94
|
+
# allow :name # permits scalar name
|
|
95
|
+
# allow :tags, array: true # permits array of scalars
|
|
96
|
+
# allow :tags, only: :create # only for create action
|
|
97
|
+
def allow(attribute, options = {})
|
|
98
|
+
attribute = attribute.to_sym
|
|
99
|
+
allowed_attributes << attribute unless allowed_attributes.include?(attribute)
|
|
100
|
+
|
|
101
|
+
# Clear cache since permitted attributes may have changed
|
|
102
|
+
@permitted_cache = {} if instance_variable_defined?(:@permitted_cache)
|
|
103
|
+
|
|
104
|
+
# Store any additional options for this attribute
|
|
105
|
+
if options.any?
|
|
106
|
+
@attribute_options ||= {}
|
|
107
|
+
# Normalize :only and :except to arrays for consistency
|
|
108
|
+
normalized_options = options.dup
|
|
109
|
+
[:only, :except].each do |key|
|
|
110
|
+
if normalized_options[key] && !normalized_options[key].is_a?(Array)
|
|
111
|
+
normalized_options[key] = [normalized_options[key]]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
@attribute_options[attribute] = normalized_options
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# DSL method to deny an attribute
|
|
119
|
+
# @param attribute [Symbol, String] the attribute name to deny
|
|
120
|
+
def deny(attribute)
|
|
121
|
+
attribute = attribute.to_sym
|
|
122
|
+
denied_attributes << attribute unless denied_attributes.include?(attribute)
|
|
123
|
+
# Clear cache since permitted attributes may have changed
|
|
124
|
+
@permitted_cache = {} if instance_variable_defined?(:@permitted_cache)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# DSL method to set a flag
|
|
128
|
+
# @param name [Symbol, String] the flag name
|
|
129
|
+
# @param value [Boolean, Object] the flag value
|
|
130
|
+
def flag(name, value = true)
|
|
131
|
+
flags[name.to_sym] = value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# DSL method to declare allowed metadata keys
|
|
135
|
+
# @param key [Symbol, String] the metadata key to allow
|
|
136
|
+
# Note: :current_user is always allowed and doesn't need to be declared
|
|
137
|
+
def metadata(*keys)
|
|
138
|
+
keys.each do |key|
|
|
139
|
+
allowed_metadata << key.to_sym
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# DSL method to define a transformation for an attribute
|
|
144
|
+
# @param attribute [Symbol, String] the attribute name to transform
|
|
145
|
+
# @param block [Proc] the transformation block
|
|
146
|
+
# The block receives two parameters:
|
|
147
|
+
# - value: the current value of the attribute
|
|
148
|
+
# - metadata: hash of metadata (current_user, action, etc.)
|
|
149
|
+
# The block should return the transformed value
|
|
150
|
+
# @example Transform email to lowercase
|
|
151
|
+
# transform :email do |value, metadata|
|
|
152
|
+
# value&.downcase&.strip
|
|
153
|
+
# end
|
|
154
|
+
# @example Conditional transformation based on metadata
|
|
155
|
+
# transform :role do |value, metadata|
|
|
156
|
+
# metadata[:current_user]&.admin? ? value : 'user'
|
|
157
|
+
# end
|
|
158
|
+
def transform(attribute, &block)
|
|
159
|
+
raise ArgumentError, "Block required for transform" unless block_given?
|
|
160
|
+
transformations[attribute.to_sym] = block
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Check if an attribute is allowed.
|
|
164
|
+
#
|
|
165
|
+
# An attribute is allowed if it's in the allowed list and not in the denied list.
|
|
166
|
+
# Uses memoization for better performance on repeated checks.
|
|
167
|
+
#
|
|
168
|
+
# @param attribute [Symbol, String] the attribute name
|
|
169
|
+
# @return [Boolean] true if allowed, false otherwise
|
|
170
|
+
def allowed?(attribute)
|
|
171
|
+
return false unless attribute.respond_to?(:to_sym)
|
|
172
|
+
attribute = attribute.to_sym
|
|
173
|
+
return false if denied_attributes.include?(attribute)
|
|
174
|
+
|
|
175
|
+
allowed_attributes.include?(attribute)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Check if an attribute is denied.
|
|
179
|
+
#
|
|
180
|
+
# @param attribute [Symbol, String] the attribute name
|
|
181
|
+
# @return [Boolean] true if denied, false otherwise
|
|
182
|
+
def denied?(attribute)
|
|
183
|
+
return false unless attribute.respond_to?(:to_sym)
|
|
184
|
+
denied_attributes.include?(attribute.to_sym)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Check if a flag is set
|
|
188
|
+
# @param name [Symbol, String] the flag name
|
|
189
|
+
# @return [Boolean, Object] the flag value
|
|
190
|
+
def flag?(name)
|
|
191
|
+
return nil unless name.respond_to?(:to_sym)
|
|
192
|
+
flags[name.to_sym]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Check if a metadata key is allowed
|
|
196
|
+
# @param key [Symbol, String] the metadata key
|
|
197
|
+
# @return [Boolean] true if allowed, false otherwise
|
|
198
|
+
# Note: :current_user is always allowed
|
|
199
|
+
def metadata_allowed?(key)
|
|
200
|
+
return false unless key.respond_to?(:to_sym)
|
|
201
|
+
key = key.to_sym
|
|
202
|
+
key == :current_user || allowed_metadata.include?(key)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Get options for an attribute
|
|
206
|
+
# @param attribute [Symbol, String] the attribute name
|
|
207
|
+
# @return [Hash] the options hash
|
|
208
|
+
def attribute_options(attribute)
|
|
209
|
+
@attribute_options ||= {}
|
|
210
|
+
return {} unless attribute.respond_to?(:to_sym)
|
|
211
|
+
@attribute_options[attribute.to_sym] || {}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Generate a permit array suitable for strong_parameters.
|
|
215
|
+
#
|
|
216
|
+
# Returns an array of permitted attributes, optionally filtered by action.
|
|
217
|
+
# Results are cached per action for better performance.
|
|
218
|
+
#
|
|
219
|
+
# @param action [Symbol, String, nil] optional action name to filter by
|
|
220
|
+
# @return [Array<Symbol, Hash>] array of permitted attributes
|
|
221
|
+
# Returns symbols for scalar attributes, and {attr: []} for array attributes
|
|
222
|
+
# @example
|
|
223
|
+
# permitted_attributes # => [:name, :email]
|
|
224
|
+
# permitted_attributes(action: :create) # => [:name, :email, :published]
|
|
225
|
+
def permitted_attributes(action: nil)
|
|
226
|
+
# Use cache for performance on repeated calls
|
|
227
|
+
@permitted_cache ||= {}
|
|
228
|
+
cache_key = action || :__no_action__
|
|
229
|
+
|
|
230
|
+
return @permitted_cache[cache_key] if @permitted_cache.key?(cache_key)
|
|
231
|
+
|
|
232
|
+
attrs = allowed_attributes.dup
|
|
233
|
+
|
|
234
|
+
# Remove denied attributes
|
|
235
|
+
attrs.reject! { |attr| denied_attributes.include?(attr) }
|
|
236
|
+
|
|
237
|
+
# Filter by action-specific flags if provided
|
|
238
|
+
if action
|
|
239
|
+
action = action.to_sym
|
|
240
|
+
end
|
|
241
|
+
attrs.select! do |attr|
|
|
242
|
+
opts = attribute_options(attr)
|
|
243
|
+
if opts[:only]
|
|
244
|
+
action.nil? || Array(opts[:only]).include?(action)
|
|
245
|
+
elsif opts[:except]
|
|
246
|
+
action.nil? || !Array(opts[:except]).include?(action)
|
|
247
|
+
else
|
|
248
|
+
true
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Convert to proper permit format
|
|
253
|
+
# For array attributes, return {attr: []}, otherwise just the symbol
|
|
254
|
+
result = attrs.map do |attr|
|
|
255
|
+
opts = attribute_options(attr)
|
|
256
|
+
if opts[:array]
|
|
257
|
+
{ attr => [] }
|
|
258
|
+
else
|
|
259
|
+
attr
|
|
260
|
+
end
|
|
261
|
+
end.freeze
|
|
262
|
+
|
|
263
|
+
@permitted_cache[cache_key] = result
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Apply transformations to a hash of parameters
|
|
267
|
+
#
|
|
268
|
+
# @param params [Hash, Parameters] the parameters to transform
|
|
269
|
+
# @param metadata [Hash] metadata hash containing current_user, action, etc.
|
|
270
|
+
# @return [Hash] the transformed parameters
|
|
271
|
+
def apply_transformations(params, metadata = {})
|
|
272
|
+
return params if transformations.empty?
|
|
273
|
+
|
|
274
|
+
# Handle non-hash params
|
|
275
|
+
return params unless params.is_a?(Hash) || params.respond_to?(:to_unsafe_h) || params.respond_to?(:to_h)
|
|
276
|
+
|
|
277
|
+
# Convert to regular hash - handle both plain hashes and Parameters objects
|
|
278
|
+
hash = if params.respond_to?(:to_unsafe_h)
|
|
279
|
+
# Rails ActionController::Parameters
|
|
280
|
+
params.to_unsafe_h
|
|
281
|
+
elsif params.respond_to?(:to_h)
|
|
282
|
+
# Core Parameters or plain Hash
|
|
283
|
+
params.to_h
|
|
284
|
+
else
|
|
285
|
+
params
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Make a dup so we don't modify the original
|
|
289
|
+
hash = hash.dup
|
|
290
|
+
|
|
291
|
+
# Apply each transformation
|
|
292
|
+
transformations.each do |attribute, transformer|
|
|
293
|
+
key_str = attribute.to_s
|
|
294
|
+
if hash.key?(key_str)
|
|
295
|
+
# Deep dup the value to prevent transformations from modifying the original
|
|
296
|
+
original_value = hash[key_str]
|
|
297
|
+
duped_value = begin
|
|
298
|
+
Marshal.load(Marshal.dump(original_value))
|
|
299
|
+
rescue TypeError
|
|
300
|
+
# If Marshal fails (e.g., due to procs or unserializable objects), shallow dup
|
|
301
|
+
original_value.dup
|
|
302
|
+
end
|
|
303
|
+
hash[key_str] = transformer.call(duped_value, metadata)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
hash
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Inherit attributes from parent class.
|
|
311
|
+
#
|
|
312
|
+
# When a subclass is created, it inherits all configuration from the parent
|
|
313
|
+
# including allowed/denied attributes, flags, metadata, transformations, and options.
|
|
314
|
+
# This enables building specialized params classes on top of base ones.
|
|
315
|
+
#
|
|
316
|
+
# @param subclass [Class] the inheriting subclass
|
|
317
|
+
# @return [void]
|
|
318
|
+
def inherited(subclass)
|
|
319
|
+
super
|
|
320
|
+
# Copy parent's configuration to subclass
|
|
321
|
+
subclass.instance_variable_set(:@allowed_attributes, allowed_attributes.dup)
|
|
322
|
+
subclass.instance_variable_set(:@denied_attributes, denied_attributes.dup)
|
|
323
|
+
subclass.instance_variable_set(:@flags, flags.dup)
|
|
324
|
+
subclass.instance_variable_set(:@allowed_metadata, allowed_metadata.dup)
|
|
325
|
+
subclass.instance_variable_set(:@transformations, transformations.dup)
|
|
326
|
+
if instance_variable_defined?(:@attribute_options)
|
|
327
|
+
subclass.instance_variable_set(:@attribute_options, @attribute_options.dup)
|
|
328
|
+
end
|
|
329
|
+
# Don't copy the cache - let subclass build its own
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrongParameters
|
|
4
|
+
module Core
|
|
5
|
+
# Configuration module for StrongParameters behavior.
|
|
6
|
+
#
|
|
7
|
+
# This module provides centralized configuration for how strong parameters
|
|
8
|
+
# behaves throughout the application, including handling of unpermitted
|
|
9
|
+
# parameters and notification mechanisms.
|
|
10
|
+
#
|
|
11
|
+
# @example Configure action on unpermitted parameters
|
|
12
|
+
# StrongParameters::Core::Configuration.action_on_unpermitted_parameters = :log
|
|
13
|
+
#
|
|
14
|
+
# @example Set custom notification handler
|
|
15
|
+
# StrongParameters::Core::Configuration.unpermitted_notification_handler = ->(keys) do
|
|
16
|
+
# Rails.logger.warn("Unpermitted parameters: #{keys.join(', ')}")
|
|
17
|
+
# end
|
|
18
|
+
module Configuration
|
|
19
|
+
class << self
|
|
20
|
+
# Action to take when unpermitted parameters are detected.
|
|
21
|
+
#
|
|
22
|
+
# **Options:**
|
|
23
|
+
# - `:log` - Log unpermitted parameters via notification handler
|
|
24
|
+
# - `:raise` - Raise UnpermittedParameters exception
|
|
25
|
+
# - `false` or `nil` - Ignore unpermitted parameters (not recommended for production)
|
|
26
|
+
#
|
|
27
|
+
# @return [Symbol, Boolean, nil] the configured action
|
|
28
|
+
#
|
|
29
|
+
# @example Enable logging
|
|
30
|
+
# Configuration.action_on_unpermitted_parameters = :log
|
|
31
|
+
#
|
|
32
|
+
# @example Enable strict mode (raise on unpermitted)
|
|
33
|
+
# Configuration.action_on_unpermitted_parameters = :raise
|
|
34
|
+
attr_accessor :action_on_unpermitted_parameters
|
|
35
|
+
|
|
36
|
+
# Handler for unpermitted parameter notifications.
|
|
37
|
+
#
|
|
38
|
+
# This proc/lambda is called when unpermitted parameters are detected
|
|
39
|
+
# and `action_on_unpermitted_parameters` is set to `:log`. The handler
|
|
40
|
+
# receives an array of unpermitted parameter keys.
|
|
41
|
+
#
|
|
42
|
+
# @return [Proc, nil] the notification handler
|
|
43
|
+
#
|
|
44
|
+
# @example Set custom handler
|
|
45
|
+
# Configuration.unpermitted_notification_handler = ->(keys) do
|
|
46
|
+
# MyLogger.warn("Unpermitted: #{keys.join(', ')}")
|
|
47
|
+
# end
|
|
48
|
+
attr_accessor :unpermitted_notification_handler
|
|
49
|
+
|
|
50
|
+
# Parameters that are never considered unpermitted.
|
|
51
|
+
#
|
|
52
|
+
# These are typically framework-added parameters that are not security
|
|
53
|
+
# concerns (like 'controller' and 'action' in Rails).
|
|
54
|
+
#
|
|
55
|
+
# @return [Array<String>] array of parameter names to always allow
|
|
56
|
+
attr_accessor :always_permitted_parameters
|
|
57
|
+
|
|
58
|
+
# Reset configuration to default values.
|
|
59
|
+
#
|
|
60
|
+
# @return [void]
|
|
61
|
+
def reset!
|
|
62
|
+
@action_on_unpermitted_parameters = nil
|
|
63
|
+
@unpermitted_notification_handler = nil
|
|
64
|
+
@always_permitted_parameters = %w[controller action].freeze
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Apply configuration to Parameters class.
|
|
68
|
+
#
|
|
69
|
+
# This method synchronizes the configuration module settings with the
|
|
70
|
+
# Parameters class class variables for backwards compatibility.
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def apply!
|
|
74
|
+
Parameters.action_on_unpermitted_parameters = action_on_unpermitted_parameters
|
|
75
|
+
Parameters.unpermitted_notification_handler = unpermitted_notification_handler
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Initialize with defaults
|
|
80
|
+
reset!
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrongParameters
|
|
4
|
+
module Core
|
|
5
|
+
# Exception raised when attempting mass assignment with unpermitted parameters.
|
|
6
|
+
#
|
|
7
|
+
# This exception is raised when Parameters are used in mass assignment
|
|
8
|
+
# without being explicitly permitted using permit() or permit!().
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# User.new(params[:user])
|
|
12
|
+
# # => StrongParameters::Core::ForbiddenAttributes (if :user params not permitted)
|
|
13
|
+
class ForbiddenAttributes < StandardError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Protection module for mass assignment.
|
|
17
|
+
#
|
|
18
|
+
# This module can be included in model classes to provide protection
|
|
19
|
+
# against unpermitted mass assignment.
|
|
20
|
+
#
|
|
21
|
+
# @example Include in a model
|
|
22
|
+
# class Post
|
|
23
|
+
# include StrongParameters::Core::ForbiddenAttributesProtection
|
|
24
|
+
#
|
|
25
|
+
# def initialize(attributes = {})
|
|
26
|
+
# assign_attributes(attributes)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# def assign_attributes(attributes)
|
|
30
|
+
# attributes = sanitize_for_mass_assignment(attributes)
|
|
31
|
+
# # ... assign attributes
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
module ForbiddenAttributesProtection
|
|
35
|
+
# Check if parameters are permitted before mass assignment.
|
|
36
|
+
#
|
|
37
|
+
# @param attributes [Object] mass assignment attributes
|
|
38
|
+
# @return [Object] the attributes if permitted
|
|
39
|
+
# @raise [ForbiddenAttributes] if parameters are not permitted
|
|
40
|
+
def sanitize_for_mass_assignment(attributes)
|
|
41
|
+
if attributes.respond_to?(:permitted?) && !attributes.permitted?
|
|
42
|
+
raise ForbiddenAttributes
|
|
43
|
+
end
|
|
44
|
+
attributes
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|