strong_parameters 0.2.0 → 0.2.1
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.md +129 -0
- data/Rakefile +2 -1
- data/lib/action_controller/parameters.rb +11 -7
- data/lib/strong_parameters.rb +1 -0
- data/lib/strong_parameters/log_subscriber.rb +14 -0
- data/lib/strong_parameters/version.rb +1 -1
- data/test/controller_generator_test.rb +0 -1
- data/test/gemfiles/Gemfile.rails-3.0.x.lock +0 -4
- data/test/parameters_permit_test.rb +19 -1
- data/test/test_helper.rb +0 -1
- metadata +4 -19
- data/README.rdoc +0 -102
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
[](http://travis-ci.org/rails/strong_parameters) [](http://badge.fury.io/rb/strong_parameters)
|
2
|
+
# Strong Parameters
|
3
|
+
|
4
|
+
With this plugin Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed.
|
5
|
+
|
6
|
+
In addition, parameters can be marked as required and flow through a predefined raise/rescue flow to end up as a 400 Bad Request with no effort.
|
7
|
+
|
8
|
+
``` ruby
|
9
|
+
class PeopleController < ActionController::Base
|
10
|
+
# This will raise an ActiveModel::ForbiddenAttributes exception because it's using mass assignment
|
11
|
+
# without an explicit permit step.
|
12
|
+
def create
|
13
|
+
Person.create(params[:person])
|
14
|
+
end
|
15
|
+
|
16
|
+
# This will pass with flying colors as long as there's a person key in the parameters, otherwise
|
17
|
+
# it'll raise a ActionController::MissingParameter exception, which will get caught by
|
18
|
+
# ActionController::Base and turned into that 400 Bad Request reply.
|
19
|
+
def update
|
20
|
+
person = current_account.people.find(params[:id])
|
21
|
+
person.update_attributes!(person_params)
|
22
|
+
redirect_to person
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# Using a private method to encapsulate the permissible parameters is just a good pattern
|
27
|
+
# since you'll be able to reuse the same permit list between create and update. Also, you
|
28
|
+
# can specialize this method with per-user checking of permissible attributes.
|
29
|
+
def person_params
|
30
|
+
params.require(:person).permit(:name, :age)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## Permitted Scalar Values
|
36
|
+
|
37
|
+
Given
|
38
|
+
|
39
|
+
``` ruby
|
40
|
+
params.permit(:id)
|
41
|
+
```
|
42
|
+
|
43
|
+
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.
|
44
|
+
|
45
|
+
The permitted scalar types are `String`, `Symbol`, `NilClass`, `Numeric`, `TrueClass`, `FalseClass`, `Date`, `Time`, `DateTime`, `StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and `Rack::Test::UploadedFile`.
|
46
|
+
|
47
|
+
To declare that the value in `params` must be an array of permitted scalar values map the key to an empty array:
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
params.permit(:id => [])
|
51
|
+
```
|
52
|
+
|
53
|
+
To whitelist an entire hash of parameters, the `permit!` method can be used
|
54
|
+
|
55
|
+
``` ruby
|
56
|
+
params.require(:log_entry).permit!
|
57
|
+
```
|
58
|
+
|
59
|
+
This will mark the `:log_entry` parameters hash and any subhash of it permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned.
|
60
|
+
|
61
|
+
## Nested Parameters
|
62
|
+
|
63
|
+
You can also use permit on nested parameters, like:
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
params.permit(:name, {:emails => []}, :friends => [ :name, { :family => [ :name ], :hobbies => [] }])
|
67
|
+
```
|
68
|
+
|
69
|
+
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).
|
70
|
+
|
71
|
+
Thanks to Nick Kallen for the permit idea!
|
72
|
+
|
73
|
+
## Handling of Unpermitted Keys
|
74
|
+
|
75
|
+
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.
|
76
|
+
|
77
|
+
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.
|
78
|
+
|
79
|
+
## Use Outside of Controllers
|
80
|
+
|
81
|
+
While Strong Parameters will enforce permitted and required values in your application controllers, keep in mind
|
82
|
+
that you will need to sanitize untrusted data used for mass assignment when in use outside of controllers.
|
83
|
+
|
84
|
+
For example, if you retrieve JSON data from a third party API call and pass the unchecked parsed result on to
|
85
|
+
`Model.create`, undesired mass assignments could take place. You can alleviate this risk by slicing the hash data,
|
86
|
+
or wrapping the data in a new instance of `ActionController::Parameters` and declaring permissions the same as
|
87
|
+
you would in a controller. For example:
|
88
|
+
|
89
|
+
``` ruby
|
90
|
+
raw_parameters = { :email => "john@example.com", :name => "John", :admin => true }
|
91
|
+
parameters = ActionController::Parameters.new(raw_parameters)
|
92
|
+
user = User.create(parameters.permit(:name, :email))
|
93
|
+
```
|
94
|
+
|
95
|
+
## Installation
|
96
|
+
|
97
|
+
In Gemfile:
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
gem 'strong_parameters'
|
101
|
+
```
|
102
|
+
|
103
|
+
and then run `bundle`. To activate the strong parameters, you need to include this module in
|
104
|
+
every model you want protected.
|
105
|
+
|
106
|
+
``` ruby
|
107
|
+
class Post < ActiveRecord::Base
|
108
|
+
include ActiveModel::ForbiddenAttributesProtection
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Alternatively, you can protect all Active Record resources by default by creating an initializer and pasting the line:
|
113
|
+
|
114
|
+
``` ruby
|
115
|
+
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
116
|
+
```
|
117
|
+
|
118
|
+
If you want to now disable the default whitelisting that occurs in Rails 3.2, change the `config.active_record.whitelist_attributes` property in your `config/application.rb`:
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
config.active_record.whitelist_attributes = false
|
122
|
+
```
|
123
|
+
|
124
|
+
This will allow you to remove / not have to use `attr_accessible` and do mass assignment inside your code and tests.
|
125
|
+
|
126
|
+
## Compatibility
|
127
|
+
|
128
|
+
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.
|
129
|
+
An unofficial Rails 2 version is [strong_parameters_rails2](https://github.com/grosser/strong_parameters/tree/rails2).
|
data/Rakefile
CHANGED
@@ -12,7 +12,8 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
12
12
|
rdoc.rdoc_dir = 'rdoc'
|
13
13
|
rdoc.title = 'StrongParameters'
|
14
14
|
rdoc.options << '--line-numbers'
|
15
|
-
|
15
|
+
rdoc_main = 'README.md'
|
16
|
+
rdoc.rdoc_files.include('README.md')
|
16
17
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
18
|
end
|
18
19
|
|
@@ -143,6 +143,7 @@ module ActionController
|
|
143
143
|
StringIO,
|
144
144
|
IO,
|
145
145
|
ActionDispatch::Http::UploadedFile,
|
146
|
+
Rack::Test::UploadedFile,
|
146
147
|
]
|
147
148
|
|
148
149
|
def permitted_scalar?(value)
|
@@ -167,9 +168,9 @@ module ActionController
|
|
167
168
|
end
|
168
169
|
end
|
169
170
|
|
170
|
-
def array_of_permitted_scalars_filter(params, key)
|
171
|
-
if has_key?(key) && array_of_permitted_scalars?(
|
172
|
-
params[key] =
|
171
|
+
def array_of_permitted_scalars_filter(params, key, hash = self)
|
172
|
+
if hash.has_key?(key) && array_of_permitted_scalars?(hash[key])
|
173
|
+
params[key] = hash[key]
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
@@ -185,10 +186,12 @@ module ActionController
|
|
185
186
|
array_of_permitted_scalars_filter(params, key)
|
186
187
|
else
|
187
188
|
# Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
|
188
|
-
params[key] = each_element(value) do |element|
|
189
|
+
params[key] = each_element(value) do |element, index|
|
189
190
|
if element.is_a?(Hash)
|
190
191
|
element = self.class.new(element) unless element.respond_to?(:permit)
|
191
192
|
element.permit(*Array.wrap(filter[key]))
|
193
|
+
elsif filter[key].is_a?(Hash) && filter[key][index] == []
|
194
|
+
array_of_permitted_scalars_filter(params, index, value)
|
192
195
|
end
|
193
196
|
end
|
194
197
|
end
|
@@ -201,7 +204,7 @@ module ActionController
|
|
201
204
|
# fields_for on an array of records uses numeric hash keys.
|
202
205
|
elsif value.is_a?(Hash) && value.keys.all? { |k| k =~ /\A-?\d+\z/ }
|
203
206
|
hash = value.class.new
|
204
|
-
value.each { |k,v| hash[k] = yield
|
207
|
+
value.each { |k,v| hash[k] = yield(v, k) }
|
205
208
|
hash
|
206
209
|
else
|
207
210
|
yield value
|
@@ -215,8 +218,9 @@ module ActionController
|
|
215
218
|
|
216
219
|
if unpermitted_keys.any?
|
217
220
|
case self.class.action_on_unpermitted_parameters
|
218
|
-
when :log
|
219
|
-
|
221
|
+
when :log
|
222
|
+
name = "unpermitted_parameters.action_controller"
|
223
|
+
ActiveSupport::Notifications.instrument(name, :keys => unpermitted_keys)
|
220
224
|
when :raise
|
221
225
|
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
|
222
226
|
end
|
data/lib/strong_parameters.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
module StrongParameters
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def unpermitted_parameters(event)
|
4
|
+
unpermitted_keys = event.payload[:keys]
|
5
|
+
debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
|
6
|
+
end
|
7
|
+
|
8
|
+
def logger
|
9
|
+
ActionController::Base.logger
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
StrongParameters::LogSubscriber.attach_to :action_controller
|
@@ -30,9 +30,6 @@ GEM
|
|
30
30
|
abstract (>= 1.0.0)
|
31
31
|
i18n (0.5.0)
|
32
32
|
json (1.7.5)
|
33
|
-
metaclass (0.0.1)
|
34
|
-
mocha (0.12.7)
|
35
|
-
metaclass (~> 0.0.1)
|
36
33
|
rack (1.2.5)
|
37
34
|
rack-mount (0.6.14)
|
38
35
|
rack (>= 1.0.0)
|
@@ -56,7 +53,6 @@ PLATFORMS
|
|
56
53
|
DEPENDENCIES
|
57
54
|
actionpack (~> 3.0.0)
|
58
55
|
activemodel (~> 3.0.0)
|
59
|
-
mocha (~> 0.12.0)
|
60
56
|
railties (~> 3.0.0)
|
61
57
|
rake
|
62
58
|
strong_parameters!
|
@@ -27,7 +27,7 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
27
27
|
values += [0, 1.0, 2**128, BigDecimal.new('1')]
|
28
28
|
values += [true, false]
|
29
29
|
values += [Date.today, Time.now, DateTime.now]
|
30
|
-
values += [StringIO.new, STDOUT, ActionDispatch::Http::UploadedFile.new(:tempfile => __FILE__)]
|
30
|
+
values += [StringIO.new, STDOUT, ActionDispatch::Http::UploadedFile.new(:tempfile => __FILE__), Rack::Test::UploadedFile.new(__FILE__)]
|
31
31
|
|
32
32
|
values.each do |value|
|
33
33
|
params = ActionController::Parameters.new(:id => value)
|
@@ -291,4 +291,22 @@ class NestedParametersTest < ActiveSupport::TestCase
|
|
291
291
|
|
292
292
|
assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death
|
293
293
|
end
|
294
|
+
|
295
|
+
test "fields_for_style_nested_params with nested arrays" do
|
296
|
+
params = ActionController::Parameters.new({
|
297
|
+
:book => {
|
298
|
+
:authors_attributes => {
|
299
|
+
:'0' => ['William Shakespeare', '52'],
|
300
|
+
:'1' => ['Unattributed Assistant']
|
301
|
+
}
|
302
|
+
}
|
303
|
+
})
|
304
|
+
permitted = params.permit :book => { :authors_attributes => { :'0' => [], :'1' => [] } }
|
305
|
+
|
306
|
+
assert_not_nil permitted[:book][:authors_attributes]['0']
|
307
|
+
assert_not_nil permitted[:book][:authors_attributes]['1']
|
308
|
+
assert_nil permitted[:book][:authors_attributes]['2']
|
309
|
+
assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][0]
|
310
|
+
assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][0]
|
311
|
+
end
|
294
312
|
end
|
data/test/test_helper.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.1
|
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: 2013-
|
12
|
+
date: 2013-05-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -75,22 +75,6 @@ dependencies:
|
|
75
75
|
- - ! '>='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: mocha
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
|
-
requirements:
|
83
|
-
- - ~>
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
version: 0.12.0
|
86
|
-
type: :development
|
87
|
-
prerelease: false
|
88
|
-
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
|
-
requirements:
|
91
|
-
- - ~>
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: 0.12.0
|
94
78
|
description:
|
95
79
|
email:
|
96
80
|
- david@heinemeierhansson.com
|
@@ -103,12 +87,13 @@ files:
|
|
103
87
|
- lib/generators/rails/strong_parameters_controller_generator.rb
|
104
88
|
- lib/generators/rails/templates/controller.rb
|
105
89
|
- lib/generators/rails/USAGE
|
90
|
+
- lib/strong_parameters/log_subscriber.rb
|
106
91
|
- lib/strong_parameters/railtie.rb
|
107
92
|
- lib/strong_parameters/version.rb
|
108
93
|
- lib/strong_parameters.rb
|
109
94
|
- MIT-LICENSE
|
110
95
|
- Rakefile
|
111
|
-
- README.
|
96
|
+
- README.md
|
112
97
|
- test/action_controller_required_params_test.rb
|
113
98
|
- test/action_controller_tainted_params_test.rb
|
114
99
|
- test/active_model_mass_assignment_taint_protection_test.rb
|
data/README.rdoc
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
= Strong Parameters
|
2
|
-
|
3
|
-
With this plugin Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed.
|
4
|
-
|
5
|
-
In addition, parameters can be marked as required and flow through a predefined raise/rescue flow to end up as a 400 Bad Request with no effort.
|
6
|
-
|
7
|
-
class PeopleController < ActionController::Base
|
8
|
-
# This will raise an ActiveModel::ForbiddenAttributes exception because it's using mass assignment
|
9
|
-
# without an explicit permit step.
|
10
|
-
def create
|
11
|
-
Person.create(params[:person])
|
12
|
-
end
|
13
|
-
|
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
|
16
|
-
# ActionController::Base and turned into that 400 Bad Request reply.
|
17
|
-
def update
|
18
|
-
person = current_account.people.find(params[:id])
|
19
|
-
person.update_attributes!(person_params)
|
20
|
-
redirect_to person
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
# Using a private method to encapsulate the permissible parameters is just a good pattern
|
25
|
-
# since you'll be able to reuse the same permit list between create and update. Also, you
|
26
|
-
# can specialize this method with per-user checking of permissible attributes.
|
27
|
-
def person_params
|
28
|
-
params.require(:person).permit(:name, :age)
|
29
|
-
end
|
30
|
-
end
|
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
|
-
|
48
|
-
You can also use permit on nested parameters, like:
|
49
|
-
|
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).
|
53
|
-
|
54
|
-
Thanks to Nick Kallen for the permit idea!
|
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
|
-
|
76
|
-
== Installation
|
77
|
-
|
78
|
-
In Gemfile:
|
79
|
-
|
80
|
-
gem 'strong_parameters'
|
81
|
-
|
82
|
-
and then run `bundle`. To activate the strong parameters, you need to include this module in
|
83
|
-
every model you want protected.
|
84
|
-
|
85
|
-
class Post < ActiveRecord::Base
|
86
|
-
include ActiveModel::ForbiddenAttributesProtection
|
87
|
-
end
|
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
|
-
|
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+:
|
94
|
-
|
95
|
-
config.active_record.whitelist_attributes = false
|
96
|
-
|
97
|
-
This will allow you to remove / not have to use +attr_accessible+ and do mass assignment inside your code and tests.
|
98
|
-
|
99
|
-
== Compatibility
|
100
|
-
|
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].
|