protected_attributes_continued 1.2.0

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