strong_parameters 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|