strong_parameters 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Travis CI](https://secure.travis-ci.org/rails/strong_parameters.png)](http://travis-ci.org/rails/strong_parameters) [![Gem Version](https://badge.fury.io/rb/strong_parameters.png)](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].
|