strong_parameters 0.1.6 → 0.2.0
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 +47 -4
- data/lib/action_controller/parameters.rb +129 -27
- data/lib/active_model/forbidden_attributes_protection.rb +0 -2
- data/lib/strong_parameters/railtie.rb +6 -0
- data/lib/strong_parameters/version.rb +1 -1
- data/test/controller_generator_test.rb +8 -8
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +88 -0
- data/test/log_on_unpermitted_params_test.rb +50 -0
- data/test/{nested_parameters_test.rb → parameters_permit_test.rb} +145 -7
- data/test/raise_on_unpermitted_params_test.rb +33 -0
- data/test/test_helper.rb +8 -0
- metadata +13 -5
data/README.rdoc
CHANGED
@@ -15,9 +15,9 @@ In addition, parameters can be marked as required and flow through a predefined
|
|
15
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
|
-
|
19
|
-
|
20
|
-
|
18
|
+
person = current_account.people.find(params[:id])
|
19
|
+
person.update_attributes!(person_params)
|
20
|
+
redirect_to person
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
@@ -29,12 +29,50 @@ In addition, parameters can be marked as required and flow through a predefined
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
== Permitted Scalar Values
|
33
|
+
|
34
|
+
Given
|
35
|
+
|
36
|
+
params.permit(:id)
|
37
|
+
|
38
|
+
the key +:id+ will pass the whitelisting if it appears in +params+ and it has a permitted scalar value associated. Otherwise the key is going to be filtered out, so arrays, hashes, or any other objects cannot be injected.
|
39
|
+
|
40
|
+
The permitted scalar types are +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, +Date+, +Time+, +DateTime+, +StringIO+, +IO+, and +ActionDispatch::Http::UploadedFile+.
|
41
|
+
|
42
|
+
To declare that the value in +params+ must be an array of permitted scalar values map the key to an empty array:
|
43
|
+
|
44
|
+
params.permit(:id => [])
|
45
|
+
|
46
|
+
== Nested Parameters
|
47
|
+
|
32
48
|
You can also use permit on nested parameters, like:
|
33
49
|
|
34
|
-
params.permit(:name,
|
50
|
+
params.permit(:name, {:emails => []}, :friends => [ :name, { :family => [ :name ], :hobbies => [] }])
|
51
|
+
|
52
|
+
This declaration whitelists the +name+, +emails+ and +friends+ attributes. It is expected that +emails+ will be an array of permitted scalar values and that +friends+ will be an array of resources with specific attributes : they should have a +name+ attribute (any permitted scalar values allowed), a +hobbies+ attribute as an array of permitted scalar values, and a +family+ attribute which is restricted to having a +name+ (any permitted scalar values allowed, too).
|
35
53
|
|
36
54
|
Thanks to Nick Kallen for the permit idea!
|
37
55
|
|
56
|
+
== Handling of Unpermitted Keys
|
57
|
+
|
58
|
+
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
|
59
|
+
|
60
|
+
Additionally, this behaviour can be changed by changing the +config.action_controller.action_on_unpermitted_parameters+ property in your environment files. If set to +:log+ the unpermitted attributes will be logged, if set to +:raise+ an exception will be raised.
|
61
|
+
|
62
|
+
== Use Outside of Controllers
|
63
|
+
|
64
|
+
While Strong Parameters will enforce permitted and required values in your application controllers, keep in mind
|
65
|
+
that you will need to sanitize untrusted data used for mass assignment when in use outside of controllers.
|
66
|
+
|
67
|
+
For example, if you retrieve JSON data from a third party API call and pass the unchecked parsed result on to
|
68
|
+
+Model.create+, undesired mass assignments could take place. You can alleviate this risk by slicing the hash data,
|
69
|
+
or wrapping the data in a new instance of +ActionController::Parameters+ and declaring permissions the same as
|
70
|
+
you would in a controller. For example:
|
71
|
+
|
72
|
+
raw_parameters = { :email => "john@example.com", :name => "John", :admin => true }
|
73
|
+
parameters = ActionController::Parameters.new(raw_parameters)
|
74
|
+
user = User.create(parameters.permit(:name, :email))
|
75
|
+
|
38
76
|
== Installation
|
39
77
|
|
40
78
|
In Gemfile:
|
@@ -48,6 +86,10 @@ every model you want protected.
|
|
48
86
|
include ActiveModel::ForbiddenAttributesProtection
|
49
87
|
end
|
50
88
|
|
89
|
+
Alternatively, you can protect all Active Record resources by default by creating an initializer and pasting the line:
|
90
|
+
|
91
|
+
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
92
|
+
|
51
93
|
If you want to now disable the default whitelisting that occurs in later versions of Rails, change the +config.active_record.whitelist_attributes+ property in your +config/application.rb+:
|
52
94
|
|
53
95
|
config.active_record.whitelist_attributes = false
|
@@ -57,3 +99,4 @@ This will allow you to remove / not have to use +attr_accessible+ and do mass as
|
|
57
99
|
== Compatibility
|
58
100
|
|
59
101
|
This plugin is only fully compatible with Rails versions 3.0, 3.1 and 3.2 but not 4.0+, as it is part of Rails Core in 4.0.
|
102
|
+
An unofficial Rails 2 version is {strong_parameters_rails2}[https://github.com/grosser/strong_parameters/tree/rails2].
|
@@ -1,6 +1,11 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'stringio'
|
4
|
+
|
1
5
|
require 'active_support/concern'
|
2
6
|
require 'active_support/core_ext/hash/indifferent_access'
|
3
7
|
require 'action_controller'
|
8
|
+
require 'action_dispatch/http/upload'
|
4
9
|
|
5
10
|
module ActionController
|
6
11
|
class ParameterMissing < IndexError
|
@@ -12,9 +17,24 @@ module ActionController
|
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
20
|
+
class UnpermittedParameters < IndexError
|
21
|
+
attr_reader :params
|
22
|
+
|
23
|
+
def initialize(params)
|
24
|
+
@params = params
|
25
|
+
super("found unpermitted parameters: #{params.join(", ")}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
15
29
|
class Parameters < ActiveSupport::HashWithIndifferentAccess
|
16
30
|
attr_accessor :permitted
|
17
31
|
alias :permitted? :permitted
|
32
|
+
|
33
|
+
cattr_accessor :action_on_unpermitted_parameters, :instance_accessor => false
|
34
|
+
|
35
|
+
# Never raise an UnpermittedParameters exception because of these params
|
36
|
+
# are present. They are added by Rails and it's of no concern.
|
37
|
+
NEVER_UNPERMITTED_PARAMS = %w( controller action )
|
18
38
|
|
19
39
|
def initialize(attributes = nil)
|
20
40
|
super(attributes)
|
@@ -42,29 +62,15 @@ module ActionController
|
|
42
62
|
|
43
63
|
filters.each do |filter|
|
44
64
|
case filter
|
45
|
-
when Symbol, String
|
46
|
-
params
|
47
|
-
keys.grep(/\A#{Regexp.escape(filter.to_s)}\(\d+[if]?\)\z/).each { |key| params[key] = self[key] }
|
65
|
+
when Symbol, String
|
66
|
+
permitted_scalar_filter(params, filter)
|
48
67
|
when Hash then
|
49
|
-
|
50
|
-
|
51
|
-
self.slice(*filter.keys).each do |key, value|
|
52
|
-
return unless value
|
53
|
-
|
54
|
-
key = key.to_sym
|
55
|
-
|
56
|
-
params[key] = each_element(value) do |value|
|
57
|
-
# filters are a Hash, so we expect value to be a Hash too
|
58
|
-
next if filter.is_a?(Hash) && !value.is_a?(Hash)
|
59
|
-
|
60
|
-
value = self.class.new(value) if !value.respond_to?(:permit)
|
61
|
-
|
62
|
-
value.permit(*Array.wrap(filter[key]))
|
63
|
-
end
|
64
|
-
end
|
68
|
+
hash_filter(params, filter)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
72
|
+
unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
|
73
|
+
|
68
74
|
params.permit!
|
69
75
|
end
|
70
76
|
|
@@ -103,6 +109,7 @@ module ActionController
|
|
103
109
|
end
|
104
110
|
|
105
111
|
private
|
112
|
+
|
106
113
|
def convert_hashes_to_parameters(key, value)
|
107
114
|
if value.is_a?(Parameters) || !value.is_a?(Hash)
|
108
115
|
value
|
@@ -112,18 +119,113 @@ module ActionController
|
|
112
119
|
end
|
113
120
|
end
|
114
121
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
+
#
|
123
|
+
# --- Filtering ----------------------------------------------------------
|
124
|
+
#
|
125
|
+
|
126
|
+
# This is a white list of permitted scalar types that includes the ones
|
127
|
+
# supported in XML and JSON requests.
|
128
|
+
#
|
129
|
+
# This list is in particular used to filter ordinary requests, String goes
|
130
|
+
# as first element to quickly short-circuit the common case.
|
131
|
+
#
|
132
|
+
# If you modify this collection please update the README.
|
133
|
+
PERMITTED_SCALAR_TYPES = [
|
134
|
+
String,
|
135
|
+
Symbol,
|
136
|
+
NilClass,
|
137
|
+
Numeric,
|
138
|
+
TrueClass,
|
139
|
+
FalseClass,
|
140
|
+
Date,
|
141
|
+
Time,
|
142
|
+
# DateTimes are Dates, we document the type but avoid the redundant check.
|
143
|
+
StringIO,
|
144
|
+
IO,
|
145
|
+
ActionDispatch::Http::UploadedFile,
|
146
|
+
]
|
147
|
+
|
148
|
+
def permitted_scalar?(value)
|
149
|
+
PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
|
150
|
+
end
|
151
|
+
|
152
|
+
def array_of_permitted_scalars?(value)
|
153
|
+
if value.is_a?(Array)
|
154
|
+
value.all? {|element| permitted_scalar?(element)}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def permitted_scalar_filter(params, key)
|
159
|
+
if has_key?(key) && permitted_scalar?(self[key])
|
160
|
+
params[key] = self[key]
|
161
|
+
end
|
162
|
+
|
163
|
+
keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/).each do |key|
|
164
|
+
if permitted_scalar?(self[key])
|
165
|
+
params[key] = self[key]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def array_of_permitted_scalars_filter(params, key)
|
171
|
+
if has_key?(key) && array_of_permitted_scalars?(self[key])
|
172
|
+
params[key] = self[key]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def hash_filter(params, filter)
|
177
|
+
filter = filter.with_indifferent_access
|
178
|
+
|
179
|
+
# Slicing filters out non-declared keys.
|
180
|
+
slice(*filter.keys).each do |key, value|
|
181
|
+
return unless value
|
182
|
+
|
183
|
+
if filter[key] == []
|
184
|
+
# Declaration {:comment_ids => []}.
|
185
|
+
array_of_permitted_scalars_filter(params, key)
|
186
|
+
else
|
187
|
+
# Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
|
188
|
+
params[key] = each_element(value) do |element|
|
189
|
+
if element.is_a?(Hash)
|
190
|
+
element = self.class.new(element) unless element.respond_to?(:permit)
|
191
|
+
element.permit(*Array.wrap(filter[key]))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def each_element(value)
|
199
|
+
if value.is_a?(Array)
|
200
|
+
value.map { |el| yield el }.compact
|
201
|
+
# fields_for on an array of records uses numeric hash keys.
|
202
|
+
elsif value.is_a?(Hash) && value.keys.all? { |k| k =~ /\A-?\d+\z/ }
|
203
|
+
hash = value.class.new
|
204
|
+
value.each { |k,v| hash[k] = yield v }
|
122
205
|
hash
|
123
206
|
else
|
124
|
-
yield
|
207
|
+
yield value
|
125
208
|
end
|
126
209
|
end
|
210
|
+
|
211
|
+
def unpermitted_parameters!(params)
|
212
|
+
return unless self.class.action_on_unpermitted_parameters
|
213
|
+
|
214
|
+
unpermitted_keys = unpermitted_keys(params)
|
215
|
+
|
216
|
+
if unpermitted_keys.any?
|
217
|
+
case self.class.action_on_unpermitted_parameters
|
218
|
+
when :log
|
219
|
+
ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}"
|
220
|
+
when :raise
|
221
|
+
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def unpermitted_keys(params)
|
227
|
+
self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
|
228
|
+
end
|
127
229
|
end
|
128
230
|
|
129
231
|
module StrongParameters
|
@@ -7,5 +7,11 @@ module StrongParameters
|
|
7
7
|
else
|
8
8
|
config.generators.scaffold_controller = :strong_parameters_controller
|
9
9
|
end
|
10
|
+
|
11
|
+
initializer "strong_parameters.config", :before => "action_controller.set_configs" do |app|
|
12
|
+
ActionController::Parameters.action_on_unpermitted_parameters = app.config.action_controller.delete(:action_on_unpermitted_parameters) do
|
13
|
+
(Rails.env.test? || Rails.env.development?) ? :log : false
|
14
|
+
end
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
@@ -14,19 +14,19 @@ class StrongParametersControllerGeneratorTest < Rails::Generators::TestCase
|
|
14
14
|
assert_file "app/controllers/users_controller.rb" do |content|
|
15
15
|
|
16
16
|
assert_instance_method :create, content do |m|
|
17
|
-
assert_match
|
18
|
-
assert_match
|
19
|
-
assert_match
|
17
|
+
assert_match '@user = User.new(user_params)', m
|
18
|
+
assert_match '@user.save', m
|
19
|
+
assert_match '@user.errors', m
|
20
20
|
end
|
21
21
|
|
22
22
|
assert_instance_method :update, content do |m|
|
23
|
-
assert_match
|
24
|
-
assert_match
|
25
|
-
assert_match
|
23
|
+
assert_match '@user = User.find(params[:id])', m
|
24
|
+
assert_match '@user.update_attributes(user_params)', m
|
25
|
+
assert_match '@user.errors', m
|
26
26
|
end
|
27
27
|
|
28
|
-
assert_match
|
29
|
-
assert_match
|
28
|
+
assert_match 'def user_params', content
|
29
|
+
assert_match 'params.require(:user).permit(:age, :name)', content
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
File without changes
|
@@ -0,0 +1,88 @@
|
|
1
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
2
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
3
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
4
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
5
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
6
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
7
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
8
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
9
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
10
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
11
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
12
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
13
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
14
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
15
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
16
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
17
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
18
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
19
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
20
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
21
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
22
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
23
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
24
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
25
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
26
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
27
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
28
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
29
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
30
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
31
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
32
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
33
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
34
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
35
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
36
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
37
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
38
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
39
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
40
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
41
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
42
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
43
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
44
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
45
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
46
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
47
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
48
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
49
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
50
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
51
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
52
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
53
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
54
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
55
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
56
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
57
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
58
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
59
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
60
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
61
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
62
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
63
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
64
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
65
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
66
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
67
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
68
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
69
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
70
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
71
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
72
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
73
|
+
[1m[36m (0.2ms)[0m [1mbegin transaction[0m
|
74
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
75
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
76
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
77
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
78
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
79
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
80
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
81
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
82
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
83
|
+
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
|
84
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
85
|
+
[1m[36m (0.3ms)[0m [1mbegin transaction[0m
|
86
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
87
|
+
[1m[36m (0.3ms)[0m [1mbegin transaction[0m
|
88
|
+
[1m[35m (0.0ms)[0m rollback transaction
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_controller/parameters'
|
3
|
+
|
4
|
+
class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
ActionController::Parameters.action_on_unpermitted_parameters = :log
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
ActionController::Parameters.action_on_unpermitted_parameters = false
|
11
|
+
end
|
12
|
+
|
13
|
+
test "logs on unexpected params" do
|
14
|
+
params = ActionController::Parameters.new({
|
15
|
+
:book => { :pages => 65 },
|
16
|
+
:fishing => "Turnips"
|
17
|
+
})
|
18
|
+
|
19
|
+
assert_logged("Unpermitted parameters: fishing") do
|
20
|
+
params.permit(:book => [:pages])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "logs on unexpected nested params" do
|
25
|
+
params = ActionController::Parameters.new({
|
26
|
+
:book => { :pages => 65, :title => "Green Cats and where to find then." }
|
27
|
+
})
|
28
|
+
|
29
|
+
assert_logged("Unpermitted parameters: title") do
|
30
|
+
params.permit(:book => [:pages])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def assert_logged(message)
|
37
|
+
old_logger = ActionController::Base.logger
|
38
|
+
log = StringIO.new
|
39
|
+
ActionController::Base.logger = Logger.new(log)
|
40
|
+
|
41
|
+
begin
|
42
|
+
yield
|
43
|
+
|
44
|
+
log.rewind
|
45
|
+
assert_match message, log.read
|
46
|
+
ensure
|
47
|
+
ActionController::Base.logger = old_logger
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,7 +1,136 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'action_controller/parameters'
|
3
|
+
require 'action_dispatch/http/upload'
|
3
4
|
|
4
5
|
class NestedParametersTest < ActiveSupport::TestCase
|
6
|
+
def assert_filtered_out(params, key)
|
7
|
+
assert !params.has_key?(key), "key #{key.inspect} has not been filtered out"
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# --- Basic interface --------------------------------------------------------
|
12
|
+
#
|
13
|
+
|
14
|
+
# --- nothing ----------------------------------------------------------------
|
15
|
+
|
16
|
+
test 'if nothing is permitted, the hash becomes empty' do
|
17
|
+
params = ActionController::Parameters.new(:id => '1234')
|
18
|
+
permitted = params.permit
|
19
|
+
permitted.permitted?
|
20
|
+
permitted.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# --- key --------------------------------------------------------------------
|
24
|
+
|
25
|
+
test 'key: permitted scalar values' do
|
26
|
+
values = ['a', :a, nil]
|
27
|
+
values += [0, 1.0, 2**128, BigDecimal.new('1')]
|
28
|
+
values += [true, false]
|
29
|
+
values += [Date.today, Time.now, DateTime.now]
|
30
|
+
values += [StringIO.new, STDOUT, ActionDispatch::Http::UploadedFile.new(:tempfile => __FILE__)]
|
31
|
+
|
32
|
+
values.each do |value|
|
33
|
+
params = ActionController::Parameters.new(:id => value)
|
34
|
+
permitted = params.permit(:id)
|
35
|
+
assert_equal value, permitted[:id]
|
36
|
+
|
37
|
+
%w(i f).each do |suffix|
|
38
|
+
params = ActionController::Parameters.new("foo(000#{suffix})" => value)
|
39
|
+
permitted = params.permit(:foo)
|
40
|
+
assert_equal value, permitted["foo(000#{suffix})"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
test 'key: unknown keys are filtered out' do
|
46
|
+
params = ActionController::Parameters.new(:id => '1234', :injected => 'injected')
|
47
|
+
permitted = params.permit(:id)
|
48
|
+
assert_equal '1234', permitted[:id]
|
49
|
+
assert_filtered_out permitted, :injected
|
50
|
+
end
|
51
|
+
|
52
|
+
test 'key: arrays are filtered out' do
|
53
|
+
[[], [1], ['1']].each do |array|
|
54
|
+
params = ActionController::Parameters.new(:id => array)
|
55
|
+
permitted = params.permit(:id)
|
56
|
+
assert_filtered_out permitted, :id
|
57
|
+
|
58
|
+
%w(i f).each do |suffix|
|
59
|
+
params = ActionController::Parameters.new("foo(000#{suffix})" => array)
|
60
|
+
permitted = params.permit(:foo)
|
61
|
+
assert_filtered_out permitted, "foo(000#{suffix})"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
test 'key: hashes are filtered out' do
|
67
|
+
[{}, {:foo => 1}, {:foo => 'bar'}].each do |hash|
|
68
|
+
params = ActionController::Parameters.new(:id => hash)
|
69
|
+
permitted = params.permit(:id)
|
70
|
+
assert_filtered_out permitted, :id
|
71
|
+
|
72
|
+
%w(i f).each do |suffix|
|
73
|
+
params = ActionController::Parameters.new("foo(000#{suffix})" => hash)
|
74
|
+
permitted = params.permit(:foo)
|
75
|
+
assert_filtered_out permitted, "foo(000#{suffix})"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
test 'key: non-permitted scalar values are filtered out' do
|
81
|
+
params = ActionController::Parameters.new(:id => Object.new)
|
82
|
+
permitted = params.permit(:id)
|
83
|
+
assert_filtered_out permitted, :id
|
84
|
+
|
85
|
+
%w(i f).each do |suffix|
|
86
|
+
params = ActionController::Parameters.new("foo(000#{suffix})" => Object.new)
|
87
|
+
permitted = params.permit(:foo)
|
88
|
+
assert_filtered_out permitted, "foo(000#{suffix})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
test 'key: it is not assigned if not present in params' do
|
93
|
+
params = ActionController::Parameters.new(:name => 'Joe')
|
94
|
+
permitted = params.permit(:id)
|
95
|
+
assert !permitted.has_key?(:id)
|
96
|
+
end
|
97
|
+
|
98
|
+
# --- key to empty array -----------------------------------------------------
|
99
|
+
|
100
|
+
test 'key to empty array: empty arrays pass' do
|
101
|
+
params = ActionController::Parameters.new(:id => [])
|
102
|
+
permitted = params.permit(:id => [])
|
103
|
+
assert_equal [], permitted[:id]
|
104
|
+
end
|
105
|
+
|
106
|
+
test 'key to empty array: arrays of permitted scalars pass' do
|
107
|
+
[['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array|
|
108
|
+
params = ActionController::Parameters.new(:id => array)
|
109
|
+
permitted = params.permit(:id => [])
|
110
|
+
assert_equal array, permitted[:id]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
test 'key to empty array: permitted scalar values do not pass' do
|
115
|
+
['foo', 1].each do |permitted_scalar|
|
116
|
+
params = ActionController::Parameters.new(:id => permitted_scalar)
|
117
|
+
permitted = params.permit(:id => [])
|
118
|
+
assert_filtered_out permitted, :id
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
test 'key to empty array: arrays of non-permitted scalar do not pass' do
|
123
|
+
[[Object.new], [[]], [[1]], [{}], [{:id => '1'}]].each do |non_permitted_scalar|
|
124
|
+
params = ActionController::Parameters.new(:id => non_permitted_scalar)
|
125
|
+
permitted = params.permit(:id => [])
|
126
|
+
assert_filtered_out permitted, :id
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# --- Nesting ----------------------------------------------------------------
|
132
|
+
#
|
133
|
+
|
5
134
|
test "permitted nested parameters" do
|
6
135
|
params = ActionController::Parameters.new({
|
7
136
|
:book => {
|
@@ -11,6 +140,8 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
11
140
|
:born => "1564-04-26"
|
12
141
|
}, {
|
13
142
|
:name => "Christopher Marlowe"
|
143
|
+
}, {
|
144
|
+
:name => %w(malicious injected names)
|
14
145
|
}],
|
15
146
|
:details => {
|
16
147
|
:pages => 200,
|
@@ -27,9 +158,12 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
27
158
|
assert_equal "William Shakespeare", permitted[:book][:authors][0][:name]
|
28
159
|
assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name]
|
29
160
|
assert_equal 200, permitted[:book][:details][:pages]
|
30
|
-
|
31
|
-
|
32
|
-
|
161
|
+
|
162
|
+
assert_filtered_out permitted[:book][:authors][2], :name
|
163
|
+
|
164
|
+
assert_filtered_out permitted, :magazine
|
165
|
+
assert_filtered_out permitted[:book][:details], :genre
|
166
|
+
assert_filtered_out permitted[:book][:authors][0], :born
|
33
167
|
end
|
34
168
|
|
35
169
|
test "permitted nested parameters with a string or a symbol as a key" do
|
@@ -64,7 +198,7 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
64
198
|
}
|
65
199
|
})
|
66
200
|
|
67
|
-
permitted = params.permit :book => :genres
|
201
|
+
permitted = params.permit :book => {:genres => []}
|
68
202
|
assert_equal ["Tragedy"], permitted[:book][:genres]
|
69
203
|
end
|
70
204
|
|
@@ -123,7 +257,8 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
123
257
|
:book => {
|
124
258
|
:authors_attributes => {
|
125
259
|
:'0' => { :name => 'William Shakespeare', :age_of_death => '52' },
|
126
|
-
:'1' => { :name => 'Unattributed Assistant' }
|
260
|
+
:'1' => { :name => 'Unattributed Assistant' },
|
261
|
+
:'2' => { :name => %w(injected names)}
|
127
262
|
}
|
128
263
|
}
|
129
264
|
})
|
@@ -131,9 +266,11 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
131
266
|
|
132
267
|
assert_not_nil permitted[:book][:authors_attributes]['0']
|
133
268
|
assert_not_nil permitted[:book][:authors_attributes]['1']
|
134
|
-
|
269
|
+
assert permitted[:book][:authors_attributes]['2'].empty?
|
135
270
|
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name]
|
136
271
|
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name]
|
272
|
+
|
273
|
+
assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death
|
137
274
|
end
|
138
275
|
|
139
276
|
test "fields_for_style_nested_params with negative numbers" do
|
@@ -149,8 +286,9 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
149
286
|
|
150
287
|
assert_not_nil permitted[:book][:authors_attributes]['-1']
|
151
288
|
assert_not_nil permitted[:book][:authors_attributes]['-2']
|
152
|
-
assert_nil permitted[:book][:authors_attributes]['-1'][:age_of_death]
|
153
289
|
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name]
|
154
290
|
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name]
|
291
|
+
|
292
|
+
assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death
|
155
293
|
end
|
156
294
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_controller/parameters'
|
3
|
+
|
4
|
+
class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
ActionController::Parameters.action_on_unpermitted_parameters = :raise
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
ActionController::Parameters.action_on_unpermitted_parameters = false
|
11
|
+
end
|
12
|
+
|
13
|
+
test "raises on unexpected params" do
|
14
|
+
params = ActionController::Parameters.new({
|
15
|
+
:book => { :pages => 65 },
|
16
|
+
:fishing => "Turnips"
|
17
|
+
})
|
18
|
+
|
19
|
+
assert_raises(ActionController::UnpermittedParameters) do
|
20
|
+
params.permit(:book => [:pages])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "raises on unexpected nested params" do
|
25
|
+
params = ActionController::Parameters.new({
|
26
|
+
:book => { :pages => 65, :title => "Green Cats and where to find then." }
|
27
|
+
})
|
28
|
+
|
29
|
+
assert_raises(ActionController::UnpermittedParameters) do
|
30
|
+
params.permit(:book => [:pages])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
ENV["RAILS_ENV"] = "test"
|
3
3
|
|
4
4
|
require 'test/unit'
|
5
|
+
require 'rails'
|
6
|
+
|
7
|
+
class FakeApplication < Rails::Application; end
|
8
|
+
|
9
|
+
Rails.application = FakeApplication
|
10
|
+
Rails.configuration.action_controller = ActiveSupport::OrderedOptions.new
|
11
|
+
|
5
12
|
require 'strong_parameters'
|
6
13
|
require 'mocha'
|
7
14
|
|
@@ -23,6 +30,7 @@ module ActionController
|
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
33
|
+
ActionController::Parameters.action_on_unpermitted_parameters = false
|
26
34
|
|
27
35
|
# Load support files
|
28
36
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_parameters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
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:
|
12
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -113,16 +113,20 @@ files:
|
|
113
113
|
- test/action_controller_tainted_params_test.rb
|
114
114
|
- test/active_model_mass_assignment_taint_protection_test.rb
|
115
115
|
- test/controller_generator_test.rb
|
116
|
+
- test/dummy/db/test.sqlite3
|
117
|
+
- test/dummy/log/test.log
|
116
118
|
- test/gemfiles/Gemfile.rails-3.0.x
|
117
119
|
- test/gemfiles/Gemfile.rails-3.0.x.lock
|
118
120
|
- test/gemfiles/Gemfile.rails-3.1.x
|
119
121
|
- test/gemfiles/Gemfile.rails-3.2.x
|
122
|
+
- test/log_on_unpermitted_params_test.rb
|
120
123
|
- test/multi_parameter_attributes_test.rb
|
121
|
-
- test/
|
124
|
+
- test/parameters_permit_test.rb
|
122
125
|
- test/parameters_require_test.rb
|
123
126
|
- test/parameters_taint_test.rb
|
127
|
+
- test/raise_on_unpermitted_params_test.rb
|
124
128
|
- test/test_helper.rb
|
125
|
-
homepage:
|
129
|
+
homepage: https://github.com/rails/strong_parameters
|
126
130
|
licenses: []
|
127
131
|
post_install_message:
|
128
132
|
rdoc_options: []
|
@@ -151,12 +155,16 @@ test_files:
|
|
151
155
|
- test/action_controller_tainted_params_test.rb
|
152
156
|
- test/active_model_mass_assignment_taint_protection_test.rb
|
153
157
|
- test/controller_generator_test.rb
|
158
|
+
- test/dummy/db/test.sqlite3
|
159
|
+
- test/dummy/log/test.log
|
154
160
|
- test/gemfiles/Gemfile.rails-3.0.x
|
155
161
|
- test/gemfiles/Gemfile.rails-3.0.x.lock
|
156
162
|
- test/gemfiles/Gemfile.rails-3.1.x
|
157
163
|
- test/gemfiles/Gemfile.rails-3.2.x
|
164
|
+
- test/log_on_unpermitted_params_test.rb
|
158
165
|
- test/multi_parameter_attributes_test.rb
|
159
|
-
- test/
|
166
|
+
- test/parameters_permit_test.rb
|
160
167
|
- test/parameters_require_test.rb
|
161
168
|
- test/parameters_taint_test.rb
|
169
|
+
- test/raise_on_unpermitted_params_test.rb
|
162
170
|
- test/test_helper.rb
|