protected_attributes_continued 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8e96d58f3d48f4636a8a6552633a10f24eb3f00e
4
+ data.tar.gz: f77843a13cf8ccd6ef72c8ed6213d84a04dc8e6a
5
+ SHA512:
6
+ metadata.gz: 04672d08b075017ca9ceaa97f1be4c409ddbd17eb818fdddd748cf72ff8f0ce9de23a1a03653675a75a6afd64c7ee965e6868a6ee5be3bb3f983e962cfc4dafc
7
+ data.tar.gz: c134172ee6b46acf19117fa6e854197d6e3833a0f7338dd56d87aa52d2f2eff773be616fdc9aacc219a8d26b228917222acc8505441477b888a06276961234ce
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012-2016 Guillermo Iguaran
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Protected Attributes Continued
2
+
3
+ [![Build Status](https://api.travis-ci.org/westonganger/protected_attributes.svg?branch=master)](https://travis-ci.org/westonganger/protected_attributes)
4
+
5
+ This is the community continued version of `protected_attributes`. I have created this new repo because the Rails team refuses to support the `protected_attributes` gem for Rails 5. It already works perfectly fine with Rails 5 it just needed the dependencies relaxed. For people who would like to continue using this feature in their Rails 5 apps lets continue the development here. This plugin was officially supported by the Rails team until the release of Rails 5.0.
6
+
7
+ Protect attributes from mass-assignment in Active Record models.
8
+
9
+ This plugin adds the class methods `attr_accessible` and `attr_protected` to your models to be able to declare white or black lists of attributes.
10
+
11
+
12
+
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's `Gemfile`:
17
+
18
+ gem 'protected_attributes_continued'
19
+
20
+ And then execute:
21
+
22
+ bundle install
23
+
24
+ ## Usage
25
+
26
+ Mass assignment security provides an interface for protecting attributes from end-user injection. This plugin provides two class methods in Active Record classes to control access to their attributes. The `attr_protected` method takes a list of attributes that will be ignored in mass-assignment.
27
+
28
+ For example:
29
+ ```ruby
30
+ attr_protected :admin
31
+ ```
32
+ `attr_protected` also optionally takes a role option using `:as` which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the `:default` role.
33
+
34
+ ```ruby
35
+ attr_protected :last_login, :as => :admin
36
+ ```
37
+ A much better way, because it follows the whitelist-principle, is the `attr_accessible` method. It is the exact opposite of `attr_protected`, because it takes a list of attributes that will be mass-assigned if present. Any other attributes will be ignored. This way you won’t forget to protect attributes when adding new ones in the course of development. Here is an example:
38
+
39
+ ```ruby
40
+ attr_accessible :name
41
+ attr_accessible :name, :is_admin, :as => :admin
42
+ ```
43
+
44
+ If you want to set a protected attribute, you will have to assign it individually:
45
+
46
+ ```ruby
47
+ params[:user] # => {:name => "owned", :is_admin => true}
48
+ @user = User.new(params[:user])
49
+ @user.is_admin # => false, not mass-assigned
50
+ @user.is_admin = true
51
+ @user.is_admin # => true
52
+ ```
53
+
54
+ When assigning attributes in Active Record using `attributes=` the `:default` role will be used. To assign attributes using different roles you should use `assign_attributes` which accepts an optional `:as` options parameter. If no `:as` option is provided then the `:default` role will be used.
55
+
56
+ You can also bypass mass-assignment security by using the `:without_protection` option. Here is an example:
57
+
58
+ ```ruby
59
+ @user = User.new
60
+
61
+ @user.assign_attributes(:name => 'Josh', :is_admin => true)
62
+ @user.name # => Josh
63
+ @user.is_admin # => false
64
+
65
+ @user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
66
+ @user.name # => Josh
67
+ @user.is_admin # => true
68
+
69
+ @user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
70
+ @user.name # => Josh
71
+ @user.is_admin # => true
72
+ ```
73
+
74
+ In a similar way, `new`, `create`, `create!`, `update_attributes` and `update_attributes!` methods all respect mass-assignment security and accept either `:as` or `:without_protection` options. For example:
75
+
76
+ ```ruby
77
+ @user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin)
78
+ @user.name # => Sebastian
79
+ @user.is_admin # => true
80
+
81
+ @user = User.create({ :name => 'Sebastian', :is_admin => true }, :without_protection => true)
82
+ @user.name # => Sebastian
83
+ @user.is_admin # => true
84
+ ```
85
+
86
+ By default the gem will use the strong parameters protection when assigning attribute, unless your model has `attr_accessible` or `attr_protected` calls.
87
+
88
+ ### Errors
89
+
90
+ By default, attributes in the params hash which are not allowed to be updated are just ignored. If you prefer an exception to be raised configure:
91
+
92
+ ```ruby
93
+ config.active_record.mass_assignment_sanitizer = :strict
94
+ ```
95
+
96
+ Any protected attributes violation raises `ActiveModel::MassAssignmentSecurity::Error` then.
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create new Pull Request
@@ -0,0 +1,31 @@
1
+ require 'active_support/concern'
2
+ require 'action_controller'
3
+ require 'action_controller/metal/params_wrapper'
4
+
5
+ module ActionController
6
+ module ParamsWrapper
7
+ class Options # :nodoc:
8
+ undef :include
9
+
10
+ def include
11
+ return super if @include_set
12
+
13
+ m = model
14
+ synchronize do
15
+ return super if @include_set
16
+
17
+ @include_set = true
18
+
19
+ unless super || exclude
20
+
21
+ if m.respond_to?(:accessible_attributes) && m.accessible_attributes(:default).present?
22
+ self.include = m.accessible_attributes(:default).to_a
23
+ elsif m.respond_to?(:attribute_names) && m.attribute_names.any?
24
+ self.include = m.attribute_names
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,362 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'active_model'
5
+ require 'active_model/mass_assignment_security/permission_set'
6
+ require 'active_model/mass_assignment_security/sanitizer'
7
+
8
+ module ActiveModel
9
+ # == Active Model Mass-Assignment Security
10
+ #
11
+ # Mass assignment security provides an interface for protecting attributes
12
+ # from end-user assignment. For more complex permissions, mass assignment
13
+ # security may be handled outside the model by extending a non-ActiveRecord
14
+ # class, such as a controller, with this behavior.
15
+ #
16
+ # For example, a logged in user may need to assign additional attributes
17
+ # depending on their role:
18
+ #
19
+ # class AccountsController < ApplicationController
20
+ # include ActiveModel::MassAssignmentSecurity
21
+ #
22
+ # attr_accessible :first_name, :last_name
23
+ # attr_accessible :first_name, :last_name, :plan_id, as: :admin
24
+ #
25
+ # def update
26
+ # ...
27
+ # @account.update_attributes(account_params)
28
+ # ...
29
+ # end
30
+ #
31
+ # protected
32
+ #
33
+ # def account_params
34
+ # role = admin ? :admin : :default
35
+ # sanitize_for_mass_assignment(params[:account], role)
36
+ # end
37
+ #
38
+ # end
39
+ #
40
+ # === Configuration options
41
+ #
42
+ # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible
43
+ # values are:
44
+ # * <tt>:logger</tt> (default) - writes filtered attributes to logger
45
+ # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt>
46
+ # on any protected attribute update.
47
+ #
48
+ # You can specify your own sanitizer object eg. <tt>MySanitizer.new</tt>.
49
+ # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for
50
+ # example implementation.
51
+ module MassAssignmentSecurity
52
+ extend ActiveSupport::Concern
53
+ include ActiveModel::ForbiddenAttributesProtection
54
+
55
+ included do
56
+ class_attribute :_accessible_attributes, instance_writer: false
57
+ class_attribute :_protected_attributes, instance_writer: false
58
+ class_attribute :_active_authorizer, instance_writer: false
59
+ class_attribute :_uses_mass_assignment_security, instance_writer: false
60
+ self._uses_mass_assignment_security = false
61
+
62
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
63
+ self.mass_assignment_sanitizer = :logger
64
+ end
65
+
66
+ module ClassMethods
67
+ # Attributes named in this macro are protected from mass-assignment
68
+ # whenever attributes are sanitized before assignment. A role for the
69
+ # attributes is optional, if no role is provided then <tt>:default</tt>
70
+ # is used. A role can be defined by using the <tt>:as</tt> option with a
71
+ # symbol or an array of symbols as the value.
72
+ #
73
+ # Mass-assignment to these attributes will simply be ignored, to assign
74
+ # to them you can use direct writer methods. This is meant to protect
75
+ # sensitive attributes from being overwritten by malicious users
76
+ # tampering with URLs or forms.
77
+ #
78
+ # class Customer
79
+ # include ActiveModel::MassAssignmentSecurity
80
+ #
81
+ # attr_accessor :name, :email, :logins_count
82
+ #
83
+ # attr_protected :logins_count
84
+ # # Suppose that admin can not change email for customer
85
+ # attr_protected :logins_count, :email, as: :admin
86
+ #
87
+ # def assign_attributes(values, options = {})
88
+ # sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
89
+ # send("#{k}=", v)
90
+ # end
91
+ # end
92
+ # end
93
+ #
94
+ # When using the <tt>:default</tt> role:
95
+ #
96
+ # customer = Customer.new
97
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5 }, as: :default)
98
+ # customer.name # => "David"
99
+ # customer.email # => "a@b.com"
100
+ # customer.logins_count # => nil
101
+ #
102
+ # And using the <tt>:admin</tt> role:
103
+ #
104
+ # customer = Customer.new
105
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5}, as: :admin)
106
+ # customer.name # => "David"
107
+ # customer.email # => nil
108
+ # customer.logins_count # => nil
109
+ #
110
+ # customer.email = 'c@d.com'
111
+ # customer.email # => "c@d.com"
112
+ #
113
+ # To start from an all-closed default and enable attributes as needed,
114
+ # have a look at +attr_accessible+.
115
+ #
116
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
117
+ # +attr_protected+ to sanitize attributes provides basically the same
118
+ # functionality, but it makes a bit tricky to deal with nested attributes.
119
+ def attr_protected(*args)
120
+ options = args.extract_options!
121
+ role = options[:as] || :default
122
+
123
+ self._protected_attributes = protected_attributes_configs.dup
124
+
125
+ Array(role).each do |name|
126
+ self._protected_attributes[name] = self.protected_attributes(name) + args
127
+ end
128
+
129
+ self._uses_mass_assignment_security = true
130
+ self._active_authorizer = self._protected_attributes
131
+ end
132
+
133
+ # Specifies a white list of model attributes that can be set via
134
+ # mass-assignment.
135
+ #
136
+ # Like +attr_protected+, a role for the attributes is optional,
137
+ # if no role is provided then <tt>:default</tt> is used. A role can be
138
+ # defined by using the <tt>:as</tt> option with a symbol or an array of
139
+ # symbols as the value.
140
+ #
141
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
142
+ # will only set attributes in this list, to assign to the rest of
143
+ # attributes you can use direct writer methods. This is meant to protect
144
+ # sensitive attributes from being overwritten by malicious users
145
+ # tampering with URLs or forms. If you'd rather start from an all-open
146
+ # default and restrict attributes as needed, have a look at
147
+ # +attr_protected+.
148
+ #
149
+ # class Customer
150
+ # include ActiveModel::MassAssignmentSecurity
151
+ #
152
+ # attr_accessor :name, :credit_rating
153
+ #
154
+ # # Both admin and default user can change name of a customer
155
+ # attr_accessible :name, as: [:admin, :default]
156
+ # # Only admin can change credit rating of a customer
157
+ # attr_accessible :credit_rating, as: :admin
158
+ #
159
+ # def assign_attributes(values, options = {})
160
+ # sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
161
+ # send("#{k}=", v)
162
+ # end
163
+ # end
164
+ # end
165
+ #
166
+ # When using the <tt>:default</tt> role:
167
+ #
168
+ # customer = Customer.new
169
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :default)
170
+ # customer.name # => "David"
171
+ # customer.credit_rating # => nil
172
+ #
173
+ # customer.credit_rating = 'Average'
174
+ # customer.credit_rating # => "Average"
175
+ #
176
+ # And using the <tt>:admin</tt> role:
177
+ #
178
+ # customer = Customer.new
179
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :admin)
180
+ # customer.name # => "David"
181
+ # customer.credit_rating # => "Excellent"
182
+ #
183
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
184
+ # +attr_accessible+ to sanitize attributes provides basically the same
185
+ # functionality, but it makes a bit tricky to deal with nested attributes.
186
+ def attr_accessible(*args)
187
+ options = args.extract_options!
188
+ role = options[:as] || :default
189
+
190
+ self._accessible_attributes = accessible_attributes_configs.dup
191
+
192
+ Array(role).each do |name|
193
+ self._accessible_attributes[name] = self.accessible_attributes(name) + args
194
+ end
195
+
196
+ self._uses_mass_assignment_security = true
197
+ self._active_authorizer = self._accessible_attributes
198
+ end
199
+
200
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::BlackList</tt>
201
+ # with the attributes protected by #attr_protected method. If no +role+
202
+ # is provided, then <tt>:default</tt> is used.
203
+ #
204
+ # class Customer
205
+ # include ActiveModel::MassAssignmentSecurity
206
+ #
207
+ # attr_accessor :name, :email, :logins_count
208
+ #
209
+ # attr_protected :logins_count
210
+ # attr_protected :logins_count, :email, as: :admin
211
+ # end
212
+ #
213
+ # Customer.protected_attributes
214
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
215
+ #
216
+ # Customer.protected_attributes(:default)
217
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
218
+ #
219
+ # Customer.protected_attributes(:admin)
220
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count", "email"}>
221
+ def protected_attributes(role = :default)
222
+ protected_attributes_configs[role]
223
+ end
224
+
225
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::WhiteList</tt>
226
+ # with the attributes protected by #attr_accessible method. If no +role+
227
+ # is provided, then <tt>:default</tt> is used.
228
+ #
229
+ # class Customer
230
+ # include ActiveModel::MassAssignmentSecurity
231
+ #
232
+ # attr_accessor :name, :credit_rating
233
+ #
234
+ # attr_accessible :name, as: [:admin, :default]
235
+ # attr_accessible :credit_rating, as: :admin
236
+ # end
237
+ #
238
+ # Customer.accessible_attributes
239
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
240
+ #
241
+ # Customer.accessible_attributes(:default)
242
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
243
+ #
244
+ # Customer.accessible_attributes(:admin)
245
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>
246
+ def accessible_attributes(role = :default)
247
+ accessible_attributes_configs[role]
248
+ end
249
+
250
+ # Returns a hash with the protected attributes (by #attr_accessible or
251
+ # #attr_protected) per role.
252
+ #
253
+ # class Customer
254
+ # include ActiveModel::MassAssignmentSecurity
255
+ #
256
+ # attr_accessor :name, :credit_rating
257
+ #
258
+ # attr_accessible :name, as: [:admin, :default]
259
+ # attr_accessible :credit_rating, as: :admin
260
+ # end
261
+ #
262
+ # Customer.active_authorizers
263
+ # # => {
264
+ # # :admin=> #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>,
265
+ # # :default=>#<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
266
+ # # }
267
+ def active_authorizers
268
+ self._active_authorizer ||= protected_attributes_configs
269
+ end
270
+ alias active_authorizer active_authorizers
271
+
272
+ # Returns an empty array by default. You can still override this to define
273
+ # the default attributes protected by #attr_protected method.
274
+ #
275
+ # class Customer
276
+ # include ActiveModel::MassAssignmentSecurity
277
+ #
278
+ # def self.attributes_protected_by_default
279
+ # [:name]
280
+ # end
281
+ # end
282
+ #
283
+ # Customer.protected_attributes
284
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {:name}>
285
+ def attributes_protected_by_default
286
+ []
287
+ end
288
+
289
+ # Defines sanitize method.
290
+ #
291
+ # class Customer
292
+ # include ActiveModel::MassAssignmentSecurity
293
+ #
294
+ # attr_accessor :name
295
+ #
296
+ # attr_protected :name
297
+ #
298
+ # def assign_attributes(values)
299
+ # sanitize_for_mass_assignment(values).each do |k, v|
300
+ # send("#{k}=", v)
301
+ # end
302
+ # end
303
+ # end
304
+ #
305
+ # # See ActiveModel::MassAssignmentSecurity::StrictSanitizer for more information.
306
+ # Customer.mass_assignment_sanitizer = :strict
307
+ #
308
+ # customer = Customer.new
309
+ # customer.assign_attributes(name: 'David')
310
+ # # => ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Customer: name
311
+ #
312
+ # Also, you can specify your own sanitizer object.
313
+ #
314
+ # class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
315
+ # def process_removed_attributes(klass, attrs)
316
+ # raise StandardError
317
+ # end
318
+ # end
319
+ #
320
+ # Customer.mass_assignment_sanitizer = CustomSanitizer.new
321
+ #
322
+ # customer = Customer.new
323
+ # customer.assign_attributes(name: 'David')
324
+ # # => StandardError: StandardError
325
+ def mass_assignment_sanitizer=(value)
326
+ self._mass_assignment_sanitizer = if value.is_a?(Symbol)
327
+ const_get(:"#{value.to_s.camelize}Sanitizer").new(self)
328
+ else
329
+ value
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ def protected_attributes_configs
336
+ self._protected_attributes ||= begin
337
+ Hash.new { |h,k| h[k] = BlackList.new(attributes_protected_by_default) }
338
+ end
339
+ end
340
+
341
+ def accessible_attributes_configs
342
+ self._accessible_attributes ||= begin
343
+ Hash.new { |h,k| h[k] = WhiteList.new }
344
+ end
345
+ end
346
+ end
347
+
348
+ protected
349
+
350
+ def sanitize_for_mass_assignment(attributes, role = nil) #:nodoc:
351
+ if _uses_mass_assignment_security
352
+ _mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role))
353
+ else
354
+ sanitize_forbidden_attributes(attributes)
355
+ end
356
+ end
357
+
358
+ def mass_assignment_authorizer(role) #:nodoc:
359
+ self.class.active_authorizer[role || :default]
360
+ end
361
+ end
362
+ end