strict_parameters 0.1.4 → 0.1.5

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.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