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 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 do |person|
18
+ redirect_to current_account.people.find(params[:id]).tap { |person|
19
19
  person.update_attributes!(person_params)
20
- end
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
- == Todos
38
+ == Installation
39
+
40
+ In Gemfile:
39
41
 
40
- * Automatically permit parameters coming from a signed form [Yehuda]
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
- check_parameters(*filters) if @strict || @@strict_config
54
- permit_parameters(*filters)
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
@@ -1,3 +1,3 @@
1
1
  module StrongParameters
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -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
@@ -2,7 +2,7 @@
2
2
  ENV["RAILS_ENV"] = "test"
3
3
 
4
4
  require 'test/unit'
5
- require 'strong_parameters'
5
+ require 'strict_parameters'
6
6
 
7
7
  module ActionController
8
8
  SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
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
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-08-20 00:00:00.000000000 Z
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