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
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
|