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
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ rescue LoadError
6
+ raise 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'StrongParameters'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc_main = 'README.md'
16
+ rdoc.rdoc_files.include('README.md')
17
+ rdoc.rdoc_files.include('lib/**/*.rb')
18
+ end
19
+
20
+ require 'rake/testtask'
21
+
22
+ Rake::TestTask.new(:test) do |t|
23
+ t.libs << 'lib'
24
+ t.libs << 'test'
25
+ t.pattern = 'test/**/*_test.rb'
26
+ t.verbose = false
27
+ end
28
+
29
+ task :default => :test
@@ -0,0 +1,38 @@
1
+ # Example params class for Account model
2
+ #
3
+ # This file demonstrates how to define permitted attributes
4
+ # for the Account model using declarative syntax.
5
+ #
6
+ # To use this example:
7
+ # 1. Rename this file to account_params.rb
8
+ # 2. Customize the allowed/denied attributes for your needs
9
+ # 3. Use in your controller with: params.require(:account).transform_params()
10
+ #
11
+ # Advanced usage:
12
+ # - Override params class: params.require(:account).transform_params(AdminAccountParams)
13
+ # - Pass current_user (always allowed): params.require(:account).transform_params(current_user: current_user)
14
+ # - Pass declared metadata: params.require(:account).transform_params(current_user: current_user, ip_address: request.ip)
15
+
16
+ class AccountParams < ApplicationParams
17
+ # Allow these attributes for mass assignment
18
+ allow :first_name
19
+ allow :last_name
20
+ allow :email
21
+ allow :phone_number
22
+
23
+ # Explicitly deny sensitive attributes
24
+ deny :is_admin
25
+ deny :account_balance
26
+
27
+ # Action-specific permissions
28
+ # allow :published_at, only: [:create, :update]
29
+ # allow :view_count, except: :create
30
+
31
+ # Declare allowed metadata keys (current_user is always allowed)
32
+ # Any metadata passed to transform_params must be declared here
33
+ metadata :ip_address, :role
34
+
35
+ # Application-specific flags
36
+ flag :require_approval, true
37
+ flag :audit_changes, true
38
+ end
@@ -0,0 +1,16 @@
1
+ # Base class for all param definitions
2
+ # Inherit from this in your app/params/*.rb files to define
3
+ # which attributes are permitted for mass assignment
4
+ #
5
+ # Example:
6
+ # class UserParams < ApplicationParams
7
+ # allow :first_name
8
+ # allow :last_name
9
+ # deny :is_admin
10
+ # end
11
+ class ApplicationParams < ActionController::ApplicationParams
12
+ # Add common permitted attributes here
13
+ # For example:
14
+ # allow :created_at
15
+ # allow :updated_at
16
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'durable_parameters/core'
4
+
5
+ module StrongParameters
6
+ module Adapters
7
+ # Hanami adapter for Strong Parameters
8
+ #
9
+ # This adapter integrates the core Strong Parameters functionality with
10
+ # Hanami applications, providing integration with Hanami actions and params.
11
+ #
12
+ # @example Basic usage in a Hanami action (Hanami 2.x)
13
+ # require 'durable_parameters/adapters/hanami'
14
+ #
15
+ # module MyApp
16
+ # module Actions
17
+ # module Users
18
+ # class Create < MyApp::Action
19
+ # include StrongParameters::Adapters::Hanami::Action
20
+ #
21
+ # def handle(request, response)
22
+ # user_params = strong_params(request.params).require(:user).permit(:name, :email)
23
+ # # ... use user_params
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ # @example Basic usage in a Hanami action (Hanami 1.x)
31
+ # require 'durable_parameters/adapters/hanami'
32
+ #
33
+ # module Web
34
+ # module Controllers
35
+ # module Users
36
+ # class Create
37
+ # include Web::Action
38
+ # include StrongParameters::Adapters::Hanami::Action
39
+ #
40
+ # def call(params)
41
+ # user_params = strong_params.require(:user).permit(:name, :email)
42
+ # # ... use user_params
43
+ # end
44
+ # end
45
+ # end
46
+ # end
47
+ # end
48
+ module Hanami
49
+ # Hanami-specific Parameters implementation
50
+ class Parameters < StrongParameters::Core::Parameters
51
+ # Hanami uses symbol keys by default
52
+ private
53
+
54
+ def normalize_key(key)
55
+ key.to_s
56
+ end
57
+ end
58
+
59
+ # Module to include in Hanami actions
60
+ module Action
61
+ # Access request parameters as a Strong Parameters object.
62
+ #
63
+ # For Hanami 2.x, pass the params object explicitly
64
+ # For Hanami 1.x, it will use the params from the action context
65
+ #
66
+ # @param params_obj [Object, nil] the params object (Hanami 2.x) or nil (Hanami 1.x)
67
+ # @return [Parameters] the request parameters wrapped in a Parameters object
68
+ def strong_params(params_obj = nil)
69
+ params_hash = if params_obj
70
+ # Hanami 2.x - params passed explicitly
71
+ params_obj.respond_to?(:to_h) ? params_obj.to_h : params_obj
72
+ elsif respond_to?(:params)
73
+ # Hanami 1.x - params available on action
74
+ params.respond_to?(:to_h) ? params.to_h : params
75
+ else
76
+ {}
77
+ end
78
+
79
+ ::StrongParameters::Adapters::Hanami::Parameters.new(params_hash)
80
+ end
81
+
82
+ # Alias for strong_params
83
+ alias sp strong_params
84
+
85
+ # Handle ParameterMissing errors (Hanami 2.x style)
86
+ def handle_parameter_missing(exception)
87
+ halt 400, { error: "Required parameter missing: #{exception.param}" }.to_json
88
+ end
89
+
90
+ # Handle ForbiddenAttributes errors (Hanami 2.x style)
91
+ def handle_forbidden_attributes(exception)
92
+ halt 400, { error: "Forbidden attributes in mass assignment" }.to_json
93
+ end
94
+
95
+ # Handle UnpermittedParameters errors (Hanami 2.x style)
96
+ def handle_unpermitted_parameters(exception)
97
+ halt 400, { error: "Unpermitted parameters: #{exception.params.join(', ')}" }.to_json
98
+ end
99
+
100
+ # Set up error handling when this module is included
101
+ def self.included(base)
102
+ # Set up automatic error handling if possible
103
+ if base.respond_to?(:handle_exception)
104
+ base.handle_exception StrongParameters::Core::ParameterMissing => :handle_parameter_missing
105
+ base.handle_exception StrongParameters::Core::ForbiddenAttributes => :handle_forbidden_attributes
106
+ base.handle_exception StrongParameters::Core::UnpermittedParameters => :handle_unpermitted_parameters
107
+ end
108
+ end
109
+ end
110
+
111
+ # Setup Hanami integration
112
+ def self.setup!(app = nil)
113
+ # Configure logging for unpermitted parameters in development
114
+ env = if app && app.respond_to?(:config)
115
+ app.config.env
116
+ elsif defined?(::Hanami) && ::Hanami.respond_to?(:env)
117
+ ::Hanami.env
118
+ else
119
+ ENV['HANAMI_ENV'] || ENV['RACK_ENV'] || 'development'
120
+ end
121
+
122
+ if env == 'development' || env == :development
123
+ ::StrongParameters::Adapters::Hanami::Parameters.action_on_unpermitted_parameters = :log
124
+ ::StrongParameters::Adapters::Hanami::Parameters.unpermitted_notification_handler = lambda do |keys|
125
+ # Try to get logger from Hanami
126
+ logger = if defined?(::Hanami) && ::Hanami.respond_to?(:logger)
127
+ ::Hanami.logger
128
+ elsif app && app.respond_to?(:logger)
129
+ app.logger
130
+ end
131
+
132
+ logger&.warn("Unpermitted parameters: #{keys.join(', ')}")
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'durable_parameters/core'
4
+
5
+ module StrongParameters
6
+ module Adapters
7
+ # Rage adapter for Strong Parameters
8
+ #
9
+ # This adapter integrates the core Strong Parameters functionality with
10
+ # Rage framework (a fast Rails-compatible web framework).
11
+ #
12
+ # @example Basic usage in a Rage controller
13
+ # require 'durable_parameters/adapters/rage'
14
+ #
15
+ # class UsersController < RageController::API
16
+ # def create
17
+ # user_params = params.require(:user).permit(:name, :email)
18
+ # # ... use user_params
19
+ # end
20
+ # end
21
+ module Rage
22
+ # Rage-specific Parameters implementation
23
+ class Parameters < StrongParameters::Core::Parameters
24
+ # Rage uses string keys like Rails
25
+ private
26
+
27
+ def normalize_key(key)
28
+ key.to_s
29
+ end
30
+ end
31
+
32
+ # Controller integration module for Strong Parameters in Rage.
33
+ #
34
+ # This module provides the params method to controllers and handles
35
+ # ParameterMissing exceptions with a 400 Bad Request response.
36
+ module Controller
37
+ # Access request parameters as a Parameters object.
38
+ #
39
+ # @return [Parameters] the request parameters wrapped in a Parameters object
40
+ def params
41
+ @_strong_params ||= begin
42
+ # Get raw params from Rage controller
43
+ raw_params = if defined?(super)
44
+ super
45
+ else
46
+ {}
47
+ end
48
+
49
+ ::StrongParameters::Adapters::Rage::Parameters.new(raw_params)
50
+ end
51
+ end
52
+
53
+ # Set the parameters for this request.
54
+ #
55
+ # @param val [Hash, Parameters] the parameters to set
56
+ # @return [Parameters] the parameters
57
+ def params=(val)
58
+ @_strong_params = val.is_a?(Hash) ? ::StrongParameters::Adapters::Rage::Parameters.new(val) : val
59
+ end
60
+
61
+ # Handle parameter missing errors
62
+ def handle_parameter_missing(exception)
63
+ render json: { error: "Required parameter missing: #{exception.param}" }, status: 400
64
+ end
65
+
66
+ # Handle forbidden attributes errors
67
+ def handle_forbidden_attributes(exception)
68
+ render json: { error: "Forbidden attributes in mass assignment" }, status: 400
69
+ end
70
+
71
+ # Handle unpermitted parameters errors
72
+ def handle_unpermitted_parameters(exception)
73
+ render json: { error: "Unpermitted parameters: #{exception.params.join(', ')}" }, status: 400
74
+ end
75
+
76
+ # Set up error handling when this module is included
77
+ def self.included(base)
78
+ # Rage uses rescue_from for error handling (Rails-compatible)
79
+ if base.respond_to?(:rescue_from)
80
+ base.rescue_from StrongParameters::Core::ParameterMissing, with: :handle_parameter_missing
81
+ base.rescue_from StrongParameters::Core::ForbiddenAttributes, with: :handle_forbidden_attributes
82
+ base.rescue_from StrongParameters::Core::UnpermittedParameters, with: :handle_unpermitted_parameters
83
+ end
84
+ end
85
+ end
86
+
87
+ # Setup Rage integration
88
+ def self.setup!
89
+ # Configure logging for unpermitted parameters
90
+ # Rage uses RAGE_ENV environment variable
91
+ env = ENV['RAGE_ENV'] || ENV['RACK_ENV'] || 'development'
92
+
93
+ if env == 'development' || env == 'test'
94
+ ::StrongParameters::Adapters::Rage::Parameters.action_on_unpermitted_parameters = :log
95
+ ::StrongParameters::Adapters::Rage::Parameters.unpermitted_notification_handler = lambda do |keys|
96
+ # Rage has a logger available
97
+ logger = if defined?(::Rage) && ::Rage.respond_to?(:logger)
98
+ ::Rage.logger
99
+ elsif defined?(::Rage::Logger)
100
+ ::Rage::Logger.logger
101
+ end
102
+
103
+ logger&.warn("Unpermitted parameters: #{keys.join(', ')}")
104
+ end
105
+ end
106
+
107
+ # Integrate with Rage controllers if available
108
+ if defined?(::RageController::API)
109
+ ::RageController::API.include(Controller)
110
+ end
111
+
112
+ # Also integrate with base controller if available
113
+ if defined?(::Rage::Controller)
114
+ ::Rage::Controller.include(Controller)
115
+ end
116
+ end
117
+
118
+ # Convenience method for manual integration
119
+ def self.included(base)
120
+ base.include Controller
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/core_ext/array/wrap'
6
+ require 'action_controller'
7
+ require 'action_dispatch/http/upload'
8
+ require 'durable_parameters/core'
9
+
10
+ module StrongParameters
11
+ module Adapters
12
+ # Rails adapter for Strong Parameters
13
+ #
14
+ # This adapter integrates the core Strong Parameters functionality with
15
+ # Rails/ActionController, providing Rails-specific features like:
16
+ # - ActiveSupport::HashWithIndifferentAccess integration
17
+ # - ActionDispatch uploaded file support
18
+ # - Rack uploaded file support
19
+ # - ActiveSupport::Concern integration
20
+ # - Automatic controller integration
21
+ module Rails
22
+ # Extension module to add Durable Parameters functionality to Rails' ActionController::Parameters
23
+ module ParametersExtension
24
+ # Track the required key for automatic params class inference
25
+ attr_accessor :required_key
26
+
27
+ # Transform and permit parameters using a declarative params class.
28
+ #
29
+ # This method provides a declarative approach to parameter filtering using
30
+ # params classes. It automatically looks up the appropriate params class
31
+ # based on the required_key if not explicitly provided.
32
+ #
33
+ # Transformations defined in the params class are applied before filtering.
34
+ #
35
+ # @param params_class [Class, Symbol, nil] optional params class or :__infer__ (default)
36
+ # @param options [Hash] metadata and configuration options
37
+ # @return [ActionController::Parameters] permitted parameters
38
+ def transform_params(params_class = :__infer__, **options)
39
+ # Extract known options
40
+ action = options[:action]
41
+ additional_attrs = options[:additional_attrs] || []
42
+
43
+ # Infer params class from required_key if not explicitly provided
44
+ if params_class == :__infer__
45
+ if instance_variable_defined?(:@required_key) && @required_key
46
+ params_class = ::ActionController::ParamsRegistry.lookup(@required_key)
47
+ else
48
+ # If no required_key and no explicit params_class, return empty permitted params
49
+ return self.class.new.permit!
50
+ end
51
+ end
52
+
53
+ # Handle case where params_class is nil
54
+ if params_class.nil?
55
+ return self.class.new.permit!
56
+ end
57
+
58
+ # Validate metadata keys (excluding known options)
59
+ metadata_keys = options.keys - [:action, :additional_attrs]
60
+ validate_metadata_keys!(params_class, metadata_keys)
61
+
62
+ # Apply transformations first (before filtering)
63
+ transformed_hash = params_class.apply_transformations(self.to_unsafe_h, options)
64
+
65
+ # Create a new Parameters object from the transformed hash
66
+ transformed_params = self.class.new(transformed_hash)
67
+
68
+ # Get permitted attributes and apply them
69
+ permitted_attrs = params_class.permitted_attributes(action: action)
70
+ permitted_attrs += additional_attrs
71
+ transformed_params.permit(*permitted_attrs)
72
+ end
73
+
74
+ # Permit parameters using a registered params class for a model.
75
+ #
76
+ # This is a convenience method that looks up the params class for the given
77
+ # model name and applies it to permit parameters.
78
+ #
79
+ # @param model_name [Symbol, String] the model name to look up
80
+ # @param action [Symbol, String, nil] optional action for filtering
81
+ # @param additional_attrs [Array<Symbol>] additional attributes to permit
82
+ # @return [ActionController::Parameters] permitted parameters
83
+ def permit_by_model(model_name, action: nil, additional_attrs: [])
84
+ params_class = ::ActionController::ParamsRegistry.lookup(model_name)
85
+ transform_params(params_class, action: action, additional_attrs: additional_attrs)
86
+ end
87
+
88
+ # Override require to track the required_key for transform_params
89
+ def require(key)
90
+ value = super(key)
91
+ # Track the required key so transform_params can infer the params class
92
+ if value.is_a?(::ActionController::Parameters)
93
+ value.instance_variable_set(:@required_key, key.to_sym)
94
+ end
95
+ value
96
+ end
97
+
98
+ # Override slice to preserve required_key
99
+ def slice(*keys)
100
+ sliced = super(*keys)
101
+ if sliced.is_a?(::ActionController::Parameters)
102
+ sliced.instance_variable_set(:@required_key, @required_key)
103
+ end
104
+ sliced
105
+ end
106
+
107
+ private
108
+
109
+ # Validate that all provided metadata keys are allowed by the params class.
110
+ def validate_metadata_keys!(params_class, metadata_keys)
111
+ return if metadata_keys.empty?
112
+
113
+ disallowed_keys = metadata_keys.reject { |key| params_class.metadata_allowed?(key) }
114
+
115
+ return unless disallowed_keys.any?
116
+
117
+ # Build a helpful error message
118
+ keys_list = disallowed_keys.map(&:inspect).join(', ')
119
+ class_name = params_class.name
120
+
121
+ raise ArgumentError, <<~ERROR.strip
122
+ Metadata key(s) #{keys_list} not allowed for #{class_name}.
123
+
124
+ To fix this, declare them in your params class:
125
+
126
+ class #{class_name} < ApplicationParams
127
+ metadata #{disallowed_keys.map(&:inspect).join(', ')}
128
+ end
129
+
130
+ Note: :current_user is always allowed and doesn't need to be declared.
131
+ ERROR
132
+ end
133
+ end
134
+
135
+ # Rails-specific Parameters implementation (deprecated, kept for compatibility)
136
+ class Parameters < StrongParameters::Core::Parameters
137
+ # Override to use ActiveSupport::HashWithIndifferentAccess behavior
138
+ def initialize(attributes = nil)
139
+ @permitted = false
140
+ @required_key = nil
141
+
142
+ if attributes
143
+ # Convert to hash with indifferent access
144
+ hash = attributes.is_a?(Hash) ? attributes.with_indifferent_access : {}
145
+ hash.each { |k, v| self[k] = v }
146
+ end
147
+ end
148
+
149
+ # Access parameter value with indifferent access (string or symbol)
150
+ def [](key)
151
+ key = key.to_s if key.is_a?(Symbol)
152
+ convert_hashes_to_parameters(key, super(key))
153
+ end
154
+
155
+ def []=(key, value)
156
+ key = key.to_s if key.is_a?(Symbol)
157
+ super(key, value)
158
+ end
159
+
160
+ def has_key?(key)
161
+ key = key.to_s if key.is_a?(Symbol)
162
+ super(key)
163
+ end
164
+
165
+ alias key? has_key?
166
+ alias include? has_key?
167
+
168
+ def delete(key)
169
+ key = key.to_s if key.is_a?(Symbol)
170
+ super(key)
171
+ end
172
+
173
+ def fetch(key, *args)
174
+ key = key.to_s if key.is_a?(Symbol)
175
+ super(key, *args)
176
+ end
177
+
178
+ private
179
+
180
+ def normalize_key(key)
181
+ key.to_s
182
+ end
183
+
184
+ # Rails-specific permitted scalar types
185
+ PERMITTED_SCALAR_TYPES = (
186
+ StrongParameters::Core::Parameters::PERMITTED_SCALAR_TYPES + [
187
+ ActionDispatch::Http::UploadedFile,
188
+ Rack::Test::UploadedFile
189
+ ]
190
+ ).freeze
191
+
192
+ def permitted_scalar?(value)
193
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
194
+ end
195
+ end
196
+
197
+ # Map core exceptions to ActionController namespace for compatibility
198
+ module ActionController
199
+ ParameterMissing = StrongParameters::Core::ParameterMissing
200
+ UnpermittedParameters = StrongParameters::Core::UnpermittedParameters
201
+
202
+ # Controller integration module for Strong Parameters in Rails.
203
+ #
204
+ # This module provides the params method to controllers and handles
205
+ # ParameterMissing exceptions with a 400 Bad Request response.
206
+ module StrongParameters
207
+ extend ActiveSupport::Concern
208
+
209
+ included do
210
+ rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
211
+ render plain: "Required parameter missing: #{parameter_missing_exception.param}",
212
+ status: :bad_request
213
+ end
214
+ end
215
+
216
+ # Access request parameters as a Parameters object.
217
+ #
218
+ # @return [Parameters] the request parameters wrapped in a Parameters object
219
+ def params
220
+ @_params ||= ::StrongParameters::Adapters::Rails::Parameters.new(request.parameters)
221
+ end
222
+
223
+ # Set the parameters for this request.
224
+ #
225
+ # @param val [Hash, Parameters] the parameters to set
226
+ # @return [Parameters] the parameters
227
+ def params=(val)
228
+ @_params = val.is_a?(Hash) ? ::StrongParameters::Adapters::Rails::Parameters.new(val) : val
229
+ end
230
+ end
231
+ end
232
+
233
+ # ActiveModel integration
234
+ module ActiveModel
235
+ ForbiddenAttributes = StrongParameters::Core::ForbiddenAttributes
236
+
237
+ # Protection module for Active Model mass assignment.
238
+ module ForbiddenAttributesProtection
239
+ # Check if parameters are permitted before mass assignment.
240
+ #
241
+ # @param options [Array] mass assignment options, first element should be attributes hash
242
+ # @return [Object] result of super if permitted
243
+ # @raise [ForbiddenAttributes] if parameters are not permitted
244
+ def sanitize_for_mass_assignment(*options)
245
+ new_attributes = options.first
246
+ if !new_attributes.respond_to?(:permitted?) || new_attributes.permitted?
247
+ super
248
+ else
249
+ raise ::StrongParameters::Core::ForbiddenAttributes
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ # Setup Rails integration
256
+ def self.setup!
257
+ # Extend the existing Rails ActionController::Parameters with our functionality
258
+ # Use prepend so our methods override existing ones
259
+ ::ActionController::Parameters.class_eval do
260
+ prepend StrongParameters::Adapters::Rails::ParametersExtension
261
+ end
262
+
263
+ # Add our new classes to ActionController namespace
264
+ ::ActionController.const_set(:ApplicationParams, ::StrongParameters::Core::ApplicationParams) unless ::ActionController.const_defined?(:ApplicationParams)
265
+ ::ActionController.const_set(:ParamsRegistry, ::StrongParameters::Core::ParamsRegistry) unless ::ActionController.const_defined?(:ParamsRegistry)
266
+
267
+ # Integrate with ActionController
268
+ ActiveSupport.on_load(:action_controller) do
269
+ include ::StrongParameters::Adapters::Rails::ActionController::StrongParameters
270
+ end
271
+
272
+ # Inject into ActiveModel using const_set (Ruby 3.5 compatible) - only if ActiveModel is loaded
273
+ if defined?(::ActiveModel)
274
+ ::ActiveModel.const_set(:ForbiddenAttributes, ::StrongParameters::Core::ForbiddenAttributes) unless ::ActiveModel.const_defined?(:ForbiddenAttributes)
275
+ ::ActiveModel.const_set(:ForbiddenAttributesProtection, ::StrongParameters::Adapters::Rails::ActiveModel::ForbiddenAttributesProtection) unless ::ActiveModel.const_defined?(:ForbiddenAttributesProtection)
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end