strict_parameters 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +17 -8
- data/lib/action_controller/parameters.rb +46 -50
- data/lib/action_controller/strict_parameters.rb +167 -0
- data/lib/strong_parameters/version.rb +1 -1
- data/test/nested_parameters_test.rb +36 -0
- data/test/parameters_strict_test.rb +17 -0
- data/test/test_helper.rb +1 -1
- metadata +3 -2
data/README.rdoc
CHANGED
@@ -10,16 +10,16 @@ In addition, parameters can be marked as required and flow through a predefined
|
|
10
10
|
def create
|
11
11
|
Person.create(params[:person])
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# This will pass with flying colors as long as there's a person key in the parameters, otherwise
|
15
|
-
# it'll raise a ActionController::MissingParameter exception, which will get caught by
|
15
|
+
# it'll raise a ActionController::MissingParameter exception, which will get caught by
|
16
16
|
# ActionController::Base and turned into that 400 Bad Request reply.
|
17
17
|
def update
|
18
|
-
redirect_to current_account.people.find(params[:id]).tap
|
18
|
+
redirect_to current_account.people.find(params[:id]).tap { |person|
|
19
19
|
person.update_attributes!(person_params)
|
20
|
-
|
20
|
+
}
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
private
|
24
24
|
# Using a private method to encapsulate the permissible parameters is just a good pattern
|
25
25
|
# since you'll be able to reuse the same permit list between create and update. Also, you
|
@@ -35,10 +35,19 @@ You can also use permit on nested parameters, like:
|
|
35
35
|
|
36
36
|
Thanks to Nick Kallen for the permit idea!
|
37
37
|
|
38
|
-
==
|
38
|
+
== Installation
|
39
|
+
|
40
|
+
In Gemfile:
|
39
41
|
|
40
|
-
|
42
|
+
gem 'strong_parameters'
|
43
|
+
|
44
|
+
and then run `bundle`. To activate the strong parameters, you need to include this module in
|
45
|
+
every model you want protected.
|
46
|
+
|
47
|
+
class Post < ActiveRecord::Base
|
48
|
+
include ActiveModel::ForbiddenAttributesProtection
|
49
|
+
end
|
41
50
|
|
42
51
|
== Compatibility
|
43
52
|
|
44
|
-
Due to a testing issue, this plugin is only fully compatible with rails/3-2-stable rev 275ee0dc7b and forward as well as rails/master rev b49a7ddce1 and forward.
|
53
|
+
Due to a testing issue, this plugin is only fully compatible with rails/3-2-stable rev 275ee0dc7b and forward as well as rails/master rev b49a7ddce1 and forward.
|
@@ -49,9 +49,48 @@ module ActionController
|
|
49
49
|
self
|
50
50
|
end
|
51
51
|
|
52
|
+
def check_parameters(*filters)
|
53
|
+
param_keys = filters.map do |filter|
|
54
|
+
filter.is_a?(Hash) ? filter.keys : filter
|
55
|
+
end.flatten.map(&:to_s)
|
56
|
+
|
57
|
+
(self.keys - param_keys).tap do |diff|
|
58
|
+
raise(ActionController::ParameterForbidden.new(diff)) unless diff.empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
52
62
|
def permit(*filters)
|
53
|
-
|
54
|
-
|
63
|
+
params = self.class.new
|
64
|
+
|
65
|
+
if @strict || @@strict_config
|
66
|
+
check_parameters(*filters)
|
67
|
+
end
|
68
|
+
|
69
|
+
filters.each do |filter|
|
70
|
+
case filter
|
71
|
+
when Symbol, String then
|
72
|
+
params[filter] = self[filter] if has_key?(filter)
|
73
|
+
when Hash then
|
74
|
+
self.slice(*filter.keys).each do |key, value|
|
75
|
+
return unless value
|
76
|
+
|
77
|
+
key = key.to_sym
|
78
|
+
|
79
|
+
params[key] = each_element(value) do |value|
|
80
|
+
# filters are a Hash, so we expect value to be a Hash too
|
81
|
+
next if filter.is_a?(Hash) && !value.is_a?(Hash)
|
82
|
+
|
83
|
+
value = self.class.new(value) if !value.respond_to?(:permit)
|
84
|
+
|
85
|
+
value.strict! if @strict
|
86
|
+
|
87
|
+
value.permit(*Array.wrap(filter[key]))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
params.permit!
|
55
94
|
end
|
56
95
|
|
57
96
|
def [](key)
|
@@ -74,54 +113,6 @@ module ActionController
|
|
74
113
|
end
|
75
114
|
end
|
76
115
|
|
77
|
-
protected
|
78
|
-
def check_parameters(*filters)
|
79
|
-
param_keys = filters.map { |filter| filter.is_a?(Hash) ? filter.keys : filter }
|
80
|
-
param_keys = param_keys.flatten.map(&:to_s)
|
81
|
-
diff = self.keys - param_keys
|
82
|
-
|
83
|
-
# exit on first mismatch
|
84
|
-
raise(ActionController::ParameterForbidden.new(diff)) unless diff.empty?
|
85
|
-
|
86
|
-
filters.each do |filter|
|
87
|
-
if filter.is_a?(Hash)
|
88
|
-
filter.each do |key,value|
|
89
|
-
params = self.class.new(self[key])
|
90
|
-
params.check_parameters(*Array.wrap(filter[key]))
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
def permit_parameters(*filters)
|
98
|
-
params = self.class.new
|
99
|
-
|
100
|
-
filters.each do |filter|
|
101
|
-
case filter
|
102
|
-
when Symbol, String then
|
103
|
-
params[filter] = self[filter] if has_key?(filter)
|
104
|
-
when Hash then
|
105
|
-
self.slice(*filter.keys).each do |key, value|
|
106
|
-
return unless value
|
107
|
-
|
108
|
-
key = key.to_sym
|
109
|
-
|
110
|
-
params[key] = each_element(value) do |value|
|
111
|
-
# filters are a Hash, so we expect value to be a Hash too
|
112
|
-
next if filter.is_a?(Hash) && !value.is_a?(Hash)
|
113
|
-
|
114
|
-
value = self.class.new(value) if !value.respond_to?(:permit)
|
115
|
-
|
116
|
-
value.permit_parameters(*Array.wrap(filter[key]))
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
params.permit!
|
123
|
-
end
|
124
|
-
|
125
116
|
private
|
126
117
|
def convert_hashes_to_parameters(key, value)
|
127
118
|
if value.is_a?(Parameters) || !value.is_a?(Hash)
|
@@ -135,6 +126,11 @@ module ActionController
|
|
135
126
|
def each_element(object)
|
136
127
|
if object.is_a?(Array)
|
137
128
|
object.map { |el| yield el }.compact
|
129
|
+
# fields_for on an array of records uses numeric hash keys
|
130
|
+
elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
|
131
|
+
hash = object.class.new
|
132
|
+
object.each { |k,v| hash[k] = yield v }
|
133
|
+
hash
|
138
134
|
else
|
139
135
|
yield object
|
140
136
|
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module ActionController
|
6
|
+
class ParameterMissing < IndexError
|
7
|
+
attr_reader :param
|
8
|
+
|
9
|
+
def initialize(param)
|
10
|
+
@param = param
|
11
|
+
super("key not found: #{param}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ParameterForbidden < IndexError
|
16
|
+
attr_reader :param
|
17
|
+
|
18
|
+
def initialize(param)
|
19
|
+
@param = param
|
20
|
+
super("key forbidden: #{param}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Parameters < ActiveSupport::HashWithIndifferentAccess
|
25
|
+
cattr_accessor :strict_config
|
26
|
+
|
27
|
+
attr_accessor :permitted
|
28
|
+
alias :permitted? :permitted
|
29
|
+
|
30
|
+
def initialize(attributes = nil)
|
31
|
+
super(attributes)
|
32
|
+
@permitted = false
|
33
|
+
@strict = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def permit!
|
37
|
+
@permitted = true
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def require(key)
|
42
|
+
self[key].presence || raise(ActionController::ParameterMissing.new(key))
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :required :require
|
46
|
+
|
47
|
+
def strict!
|
48
|
+
@strict = true
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def chck_parameters(*filters)
|
54
|
+
param_keys = filters.map do |filter|
|
55
|
+
filter.is_a?(Hash) ? filter.keys : filter
|
56
|
+
end.flatten.map(&:to_s)
|
57
|
+
|
58
|
+
(self.keys - param_keys).tap do |diff|
|
59
|
+
unless diff.empty?
|
60
|
+
raise(ActionController::ParameterForbidden.new(diff))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def permit(*filters)
|
67
|
+
params = self.class.new
|
68
|
+
|
69
|
+
if @strict || @@strict_config
|
70
|
+
check_parameters(*filters)
|
71
|
+
end
|
72
|
+
|
73
|
+
filters.each do |filter|
|
74
|
+
case filter
|
75
|
+
when Symbol, String then
|
76
|
+
params[filter] = self[filter] if has_key?(filter)
|
77
|
+
when Hash then
|
78
|
+
self.slice(*filter.keys).each do |key, value|
|
79
|
+
return unless value
|
80
|
+
|
81
|
+
key = key.to_sym
|
82
|
+
|
83
|
+
params[key] = each_element(value) do |value|
|
84
|
+
# filters are a Hash, so we expect value to be a Hash too
|
85
|
+
next if filter.is_a?(Hash) && !value.is_a?(Hash)
|
86
|
+
|
87
|
+
value = self.class.new(value) if !value.respond_to?(:permit)
|
88
|
+
|
89
|
+
value.strict! if @strict
|
90
|
+
|
91
|
+
value.permit(*Array.wrap(filter[key]))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
params.permit!
|
98
|
+
end
|
99
|
+
|
100
|
+
def [](key)
|
101
|
+
convert_hashes_to_parameters(key, super)
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetch(key, *args)
|
105
|
+
convert_hashes_to_parameters(key, super)
|
106
|
+
rescue KeyError
|
107
|
+
raise ActionController::ParameterMissing.new(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
def slice(*keys)
|
111
|
+
self.class.new(super)
|
112
|
+
end
|
113
|
+
|
114
|
+
def dup
|
115
|
+
super.tap do |duplicate|
|
116
|
+
duplicate.instance_variable_set :@permitted, @permitted
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def convert_hashes_to_parameters(key, value)
|
122
|
+
if value.is_a?(Parameters) || !value.is_a?(Hash)
|
123
|
+
value
|
124
|
+
else
|
125
|
+
# Convert to Parameters on first access
|
126
|
+
self[key] = self.class.new(value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def each_element(object)
|
131
|
+
if object.is_a?(Array)
|
132
|
+
object.map { |el| yield el }.compact
|
133
|
+
# fields_for on an array of records uses numeric hash keys
|
134
|
+
elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
|
135
|
+
hash = object.class.new
|
136
|
+
object.each { |k,v| hash[k] = yield v }
|
137
|
+
hash
|
138
|
+
else
|
139
|
+
yield object
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
module StrongParameters
|
145
|
+
extend ActiveSupport::Concern
|
146
|
+
|
147
|
+
included do
|
148
|
+
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
|
149
|
+
render :text => "Required parameter missing: #{parameter_missing_exception.param}", :status => :bad_request
|
150
|
+
end
|
151
|
+
|
152
|
+
rescue_from(ActionController::ParameterForbidden) do |parameter_forbidden_exception|
|
153
|
+
render :text => "Parameters forbidden: #{parameter_forbidden_exception.param.join(' ')}", :status => :unprocessable_entity
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def params
|
158
|
+
@_params ||= Parameters.new(request.parameters)
|
159
|
+
end
|
160
|
+
|
161
|
+
def params=(val)
|
162
|
+
@_params = val.is_a?(Hash) ? Parameters.new(val) : val
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
ActionController::Base.send :include, ActionController::StrongParameters
|
@@ -92,4 +92,40 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
92
92
|
permitted = params.permit book: { genre: :type }
|
93
93
|
assert_nil permitted[:book][:genre]
|
94
94
|
end
|
95
|
+
|
96
|
+
test "fields_for_style_nested_params" do
|
97
|
+
params = ActionController::Parameters.new({
|
98
|
+
book: {
|
99
|
+
authors_attributes: {
|
100
|
+
:'0' => { name: 'William Shakespeare', age_of_death: '52' },
|
101
|
+
:'1' => { name: 'Unattributed Assistant' }
|
102
|
+
}
|
103
|
+
}
|
104
|
+
})
|
105
|
+
permitted = params.permit book: { authors_attributes: [ :name ] }
|
106
|
+
|
107
|
+
assert_not_nil permitted[:book][:authors_attributes]['0']
|
108
|
+
assert_not_nil permitted[:book][:authors_attributes]['1']
|
109
|
+
assert_nil permitted[:book][:authors_attributes]['0'][:age_of_death]
|
110
|
+
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name]
|
111
|
+
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name]
|
112
|
+
end
|
113
|
+
|
114
|
+
test "fields_for_style_nested_params with negative numbers" do
|
115
|
+
params = ActionController::Parameters.new({
|
116
|
+
book: {
|
117
|
+
authors_attributes: {
|
118
|
+
:'-1' => {name: 'William Shakespeare', age_of_death: '52'},
|
119
|
+
:'-2' => {name: 'Unattributed Assistant'}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
})
|
123
|
+
permitted = params.permit book: {authors_attributes: [:name]}
|
124
|
+
|
125
|
+
assert_not_nil permitted[:book][:authors_attributes]['-1']
|
126
|
+
assert_not_nil permitted[:book][:authors_attributes]['-2']
|
127
|
+
assert_nil permitted[:book][:authors_attributes]['-1'][:age_of_death]
|
128
|
+
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name]
|
129
|
+
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name]
|
130
|
+
end
|
95
131
|
end
|
@@ -71,4 +71,21 @@ class ParametersStrictTest < ActiveSupport::TestCase
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
test "strict! fields_for_style_nested_params with negative numbers" do
|
75
|
+
params = ActionController::Parameters.new({
|
76
|
+
book: {
|
77
|
+
authors_attributes: {
|
78
|
+
:'-1' => {name: 'William Shakespeare', age_of_death: '52'},
|
79
|
+
:'-2' => {name: 'Unattributed Assistant'}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
})
|
83
|
+
permitted = params.permit book: {authors_attributes: [:name, :age_of_death]}
|
84
|
+
|
85
|
+
assert_not_nil permitted[:book][:authors_attributes]['-1']
|
86
|
+
assert_not_nil permitted[:book][:authors_attributes]['-2']
|
87
|
+
assert_not_nil permitted[:book][:authors_attributes]['-1'][:age_of_death]
|
88
|
+
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name]
|
89
|
+
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name]
|
90
|
+
end
|
74
91
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict_parameters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -83,6 +83,7 @@ extensions: []
|
|
83
83
|
extra_rdoc_files: []
|
84
84
|
files:
|
85
85
|
- lib/action_controller/parameters.rb
|
86
|
+
- lib/action_controller/strict_parameters.rb
|
86
87
|
- lib/active_model/forbidden_attributes_protection.rb
|
87
88
|
- lib/generators/rails/strong_parameters_controller_generator.rb
|
88
89
|
- lib/generators/rails/templates/controller.rb
|