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