mass_assignment_with_multiple_roles 0.0.1
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/.gitignore +19 -0
- data/.rvmrc +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +82 -0
- data/Rakefile +23 -0
- data/lib/mass_assignment_with_multiple_roles.rb +65 -0
- data/lib/mass_assignment_with_multiple_roles/mass_assignment_security.rb +57 -0
- data/lib/mass_assignment_with_multiple_roles/version.rb +3 -0
- data/mass_assignment_with_multiple_roles.gemspec +22 -0
- data/test/cases/helper.rb +16 -0
- data/test/cases/mass_assignment_security_test.rb +120 -0
- data/test/cases/mass_assignment_security_with_multiple_roles_test.rb +99 -0
- data/test/config.rb +3 -0
- data/test/models/administrator.rb +10 -0
- data/test/models/automobile.rb +12 -0
- data/test/models/blog_post.rb +9 -0
- data/test/models/contact.rb +26 -0
- data/test/models/custom_reader.rb +15 -0
- data/test/models/helicopter.rb +3 -0
- data/test/models/mass_assignment_specific.rb +102 -0
- data/test/models/observers.rb +27 -0
- data/test/models/person.rb +17 -0
- data/test/models/person_with_validator.rb +24 -0
- data/test/models/reply.rb +32 -0
- data/test/models/sheep.rb +3 -0
- data/test/models/topic.rb +40 -0
- data/test/models/track_back.rb +11 -0
- data/test/models/user.rb +8 -0
- data/test/models/visitor.rb +9 -0
- metadata +168 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 saksmlz
|
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,82 @@
|
|
1
|
+
# MassAssignmentWithMultipleRoles [](http://travis-ci.org/#!/saks/mass_assignment_with_multiple_roles)
|
2
|
+
|
3
|
+
Quite often your user can have multiple roles at the same time. `ActiveModel` does not provide the ability to sanitize attributes using intersection of `attr_accessible` for several role names at the same time.
|
4
|
+
|
5
|
+
This gem is all about to solve this problem.
|
6
|
+
|
7
|
+
## Example of usage
|
8
|
+
|
9
|
+
Consider we have the following model code:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class Student < ActiveRecord::Base
|
13
|
+
|
14
|
+
attr_accessible :phone_number
|
15
|
+
attr_accessible :name, as: :admin
|
16
|
+
attr_accessible :email, as: :user
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
|
21
|
+
Now, while updating in controller we can write:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
student = Student.find(params[:id])
|
25
|
+
if student.update_attributes(params[:student], :as => [:admin, :teacher])
|
26
|
+
```
|
27
|
+
|
28
|
+
Role names can be passed in `ActiveModel` style:
|
29
|
+
```ruby
|
30
|
+
student.update_attributes(params[:student], :as => :admin)
|
31
|
+
```
|
32
|
+
|
33
|
+
all it's functionality is fully supported.
|
34
|
+
|
35
|
+
Several roles can be passed as an array symbols:
|
36
|
+
```ruby
|
37
|
+
student.update_attributes(params[:student], :as => [:admin, :teacher])
|
38
|
+
```
|
39
|
+
|
40
|
+
But in most cases, role names can be obtained from user model. If your user
|
41
|
+
model provides method called `role_names`, which returns as array of role
|
42
|
+
names, you can write code which is quite easy to understand.
|
43
|
+
|
44
|
+
In model:
|
45
|
+
```ruby
|
46
|
+
class User < ActiveRecord::Base
|
47
|
+
has_many :roles
|
48
|
+
|
49
|
+
def role_names
|
50
|
+
roles.map &:name
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
In controller:
|
57
|
+
```ruby
|
58
|
+
student.update_attributes(params[:student], :as => current_user)
|
59
|
+
```
|
60
|
+
|
61
|
+
|
62
|
+
## Installation
|
63
|
+
|
64
|
+
Add this line to your application's Gemfile:
|
65
|
+
|
66
|
+
gem 'mass_assignment_with_multiple_roles'
|
67
|
+
|
68
|
+
And then execute:
|
69
|
+
|
70
|
+
$ bundle
|
71
|
+
|
72
|
+
Or install it yourself as:
|
73
|
+
|
74
|
+
$ gem install mass_assignment_with_multiple_roles
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << "test"
|
12
|
+
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
|
13
|
+
t.warning = true
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :test do
|
17
|
+
task :isolated do
|
18
|
+
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
|
19
|
+
Dir.glob("#{dir}/test/**/*_test.rb").all? do |file|
|
20
|
+
sh(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file)
|
21
|
+
end or raise "Failures"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'mass_assignment_with_multiple_roles/version'
|
3
|
+
|
4
|
+
module MassAssignmentWithMultipleRoles
|
5
|
+
ROLE_NAMES_METHOD = :role_names
|
6
|
+
|
7
|
+
def compile_role_name(roles, active_authorizer)
|
8
|
+
roles_array = if roles.is_a? Array
|
9
|
+
roles
|
10
|
+
elsif roles.respond_to? ROLE_NAMES_METHOD
|
11
|
+
[roles.send(ROLE_NAMES_METHOD)].flatten
|
12
|
+
else
|
13
|
+
[roles]
|
14
|
+
end
|
15
|
+
|
16
|
+
roles_array = roles_array.find_all { |role| active_authorizer.has_key? role }
|
17
|
+
|
18
|
+
if roles_array.any?
|
19
|
+
[ roles_array.map(&:to_s).join('_').to_sym, roles_array ]
|
20
|
+
else
|
21
|
+
[ nil, roles_array ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_new_attrs_config(roles_array, new_role_name, klass)
|
26
|
+
fields = roles_array.inject([]) do |result, role_name|
|
27
|
+
result += klass.active_authorizer[role_name].to_a
|
28
|
+
end
|
29
|
+
|
30
|
+
method_name = if klass.active_authorizer[ roles_array[0] ].is_a? ActiveModel::MassAssignmentSecurity::WhiteList
|
31
|
+
:attr_accessible
|
32
|
+
else
|
33
|
+
:attr_protected
|
34
|
+
end
|
35
|
+
|
36
|
+
klass.send method_name, *fields, :as => new_role_name
|
37
|
+
end
|
38
|
+
|
39
|
+
extend self
|
40
|
+
end
|
41
|
+
|
42
|
+
module ActiveModel
|
43
|
+
module MassAssignmentSecurity
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def mass_assignment_authorizer(roles)
|
48
|
+
active_authorizer = self.class.active_authorizer
|
49
|
+
|
50
|
+
composite_role_name, roles_array = MassAssignmentWithMultipleRoles::compile_role_name roles, active_authorizer
|
51
|
+
|
52
|
+
if !composite_role_name
|
53
|
+
active_authorizer[:default]
|
54
|
+
|
55
|
+
elsif active_authorizer.has_key? composite_role_name
|
56
|
+
active_authorizer[composite_role_name]
|
57
|
+
|
58
|
+
else
|
59
|
+
MassAssignmentWithMultipleRoles::build_new_attrs_config roles_array, composite_role_name, self.class
|
60
|
+
self.class.active_authorizer[composite_role_name]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module MassAssignmentSecurity
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def mass_assignment_authorizer(roles)
|
7
|
+
active_authorizer = self.class.active_authorizer
|
8
|
+
|
9
|
+
composite_role_name, roles_array = compile_role_name roles, active_authorizer
|
10
|
+
|
11
|
+
if !composite_role_name
|
12
|
+
active_authorizer[:default]
|
13
|
+
|
14
|
+
elsif active_authorizer.has_key? composite_role_name
|
15
|
+
active_authorizer[composite_role_name]
|
16
|
+
|
17
|
+
else
|
18
|
+
build_new_attrs_config roles_array, composite_role_name
|
19
|
+
self.class.active_authorizer[composite_role_name]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def compile_role_name(roles, active_authorizer)
|
26
|
+
roles_array = if roles.is_a? Array
|
27
|
+
roles
|
28
|
+
elsif roles.respond_to? :roles
|
29
|
+
[roles.send(:roles)].flatten
|
30
|
+
else
|
31
|
+
[roles]
|
32
|
+
end
|
33
|
+
|
34
|
+
roles_array = roles_array.find_all { |role| active_authorizer.has_key? role }
|
35
|
+
|
36
|
+
if roles_array.any?
|
37
|
+
[ roles_array.map(&:to_s).join('_').to_sym, roles_array ]
|
38
|
+
else
|
39
|
+
[ nil, roles_array ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_new_attrs_config(roles_array, new_role_name)
|
44
|
+
fields = roles_array.inject([]) do |result, role_name|
|
45
|
+
result += self.class.active_authorizer[role_name].to_a
|
46
|
+
end
|
47
|
+
|
48
|
+
method_name = if self.class.active_authorizer[ roles_array[0] ].is_a? ActiveModel::MassAssignmentSecurity::WhiteList
|
49
|
+
:attr_accessible
|
50
|
+
else
|
51
|
+
:attr_protected
|
52
|
+
end
|
53
|
+
|
54
|
+
self.class.send method_name, *fields, :as => new_role_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/mass_assignment_with_multiple_roles/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["saksmlz"]
|
6
|
+
gem.email = ["saksmlz@gmail.com"]
|
7
|
+
gem.description = %q{This gem allows you to pass multiple roles into methods like save and update_attributes}
|
8
|
+
gem.summary = %q{Allows to use intersection of attr_accessible if passing multiple role names on save}
|
9
|
+
gem.homepage = "http://github.com/saks/mass_assignment_with_multiple_roles"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "mass_assignment_with_multiple_roles"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = MassAssignmentWithMultipleRoles::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'activesupport', '~> 3.2.3'
|
19
|
+
gem.add_development_dependency 'mocha', '> 0'
|
20
|
+
gem.add_development_dependency 'rake', '> 0'
|
21
|
+
gem.add_dependency 'activemodel', '~> 3.2.3'
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# require File.expand_path('../../../../load_paths', __FILE__)
|
2
|
+
|
3
|
+
lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib")
|
4
|
+
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
|
5
|
+
|
6
|
+
require 'config'
|
7
|
+
require 'active_model'
|
8
|
+
require 'active_support/core_ext/string/access'
|
9
|
+
require 'mass_assignment_with_multiple_roles'
|
10
|
+
require 'active_model/mass_assignment_security/permission_set'
|
11
|
+
require 'active_model/mass_assignment_security/sanitizer'
|
12
|
+
|
13
|
+
# Show backtraces for deprecated behavior for quicker cleanup.
|
14
|
+
ActiveSupport::Deprecation.debug = true
|
15
|
+
|
16
|
+
require 'test/unit'
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
require 'models/mass_assignment_specific'
|
3
|
+
|
4
|
+
|
5
|
+
class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
|
6
|
+
|
7
|
+
def process_removed_attributes(attrs)
|
8
|
+
raise StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class MassAssignmentSecurityTest < ActiveModel::TestCase
|
14
|
+
|
15
|
+
def test_attribute_protection
|
16
|
+
user = User.new
|
17
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
18
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
19
|
+
assert_equal expected, sanitized
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_attribute_protection_when_role_is_nil
|
23
|
+
user = User.new
|
24
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
25
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil)
|
26
|
+
assert_equal expected, sanitized
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_only_moderator_role_attribute_accessible
|
30
|
+
user = SpecialUser.new
|
31
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
32
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator)
|
33
|
+
assert_equal expected, sanitized
|
34
|
+
|
35
|
+
sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true })
|
36
|
+
assert_equal({}, sanitized)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_attributes_accessible
|
40
|
+
user = Person.new
|
41
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
42
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
43
|
+
assert_equal expected, sanitized
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_attributes_accessible_with_admin_role
|
47
|
+
user = Person.new
|
48
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
|
49
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
|
50
|
+
assert_equal expected, sanitized
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_attributes_accessible_with_roles_given_as_array
|
54
|
+
user = Account.new
|
55
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com" }
|
56
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
|
57
|
+
assert_equal expected, sanitized
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_attributes_accessible_with_admin_role_when_roles_given_as_array
|
61
|
+
user = Account.new
|
62
|
+
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
|
63
|
+
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
|
64
|
+
assert_equal expected, sanitized
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_attributes_protected_by_default
|
68
|
+
firm = Firm.new
|
69
|
+
expected = { }
|
70
|
+
sanitized = firm.sanitize_for_mass_assignment({ "type" => "Client" })
|
71
|
+
assert_equal expected, sanitized
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_mass_assignment_protection_inheritance
|
75
|
+
assert_blank LoosePerson.accessible_attributes
|
76
|
+
assert_equal Set.new(['credit_rating', 'administrator']), LoosePerson.protected_attributes
|
77
|
+
|
78
|
+
assert_blank LoosePerson.accessible_attributes
|
79
|
+
assert_equal Set.new(['credit_rating']), LoosePerson.protected_attributes(:admin)
|
80
|
+
|
81
|
+
assert_blank LooseDescendant.accessible_attributes
|
82
|
+
assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes
|
83
|
+
|
84
|
+
assert_blank LooseDescendantSecond.accessible_attributes
|
85
|
+
assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
|
86
|
+
'Running attr_protected twice in one class should merge the protections'
|
87
|
+
|
88
|
+
assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default
|
89
|
+
assert_equal Set.new(['name', 'address']), TightPerson.accessible_attributes
|
90
|
+
|
91
|
+
assert_blank TightPerson.protected_attributes(:admin) - TightPerson.attributes_protected_by_default
|
92
|
+
assert_equal Set.new(['name', 'address', 'admin']), TightPerson.accessible_attributes(:admin)
|
93
|
+
|
94
|
+
assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default
|
95
|
+
assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes
|
96
|
+
|
97
|
+
assert_blank TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default
|
98
|
+
assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin)
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_mass_assignment_multiparameter_protector
|
103
|
+
task = Task.new
|
104
|
+
attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
|
105
|
+
sanitized = task.sanitize_for_mass_assignment(attributes)
|
106
|
+
assert_equal sanitized, { }
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_custom_sanitizer
|
110
|
+
user = User.new
|
111
|
+
User.mass_assignment_sanitizer = CustomSanitizer.new
|
112
|
+
assert_raise StandardError do
|
113
|
+
user.sanitize_for_mass_assignment("admin" => true)
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
User.mass_assignment_sanitizer = nil
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
require 'models/mass_assignment_specific'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
class MassAssignmentSecurityWithMultipleRolesTest < ActiveModel::TestCase
|
7
|
+
def setup
|
8
|
+
Student.active_authorizer.delete :admin_user
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_attribute_protection_when_role_is_nil
|
12
|
+
student = Student.new
|
13
|
+
expected = { 'name' => 'buz' }
|
14
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'), [nil, :admin])
|
15
|
+
assert_equal expected, sanitized
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_it_falls_to_default_if_no_valid_role_was_passed
|
19
|
+
student = Student.new
|
20
|
+
expected = { 'phone_number' => 'buz' }
|
21
|
+
|
22
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'), [])
|
23
|
+
assert_equal expected, sanitized
|
24
|
+
|
25
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'), nil)
|
26
|
+
assert_equal expected, sanitized
|
27
|
+
|
28
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'))
|
29
|
+
assert_equal expected, sanitized
|
30
|
+
|
31
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'), [Object.new])
|
32
|
+
assert_equal expected, sanitized
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_with_an_array_of_roles_for_attr_accesible
|
36
|
+
student = Student.new
|
37
|
+
expected = { 'name' => 'buz', 'email' => 'foo' }
|
38
|
+
sanitized = student.sanitize_for_mass_assignment(expected, [:user, :admin])
|
39
|
+
assert_equal expected, sanitized
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_with_an_array_of_roles_for_attr_protected
|
43
|
+
student = Teacher.new
|
44
|
+
expected = { 'phone_number' => 'bar' }
|
45
|
+
|
46
|
+
sanitized = student.sanitize_for_mass_assignment(
|
47
|
+
expected.merge('name' => 'buz', 'email' => 'foo'),
|
48
|
+
[:user, :admin]
|
49
|
+
)
|
50
|
+
|
51
|
+
assert_equal expected, sanitized
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_with_object_that_respond_to_roles_method
|
55
|
+
student = Student.new
|
56
|
+
user = OpenStruct.new MassAssignmentWithMultipleRoles::ROLE_NAMES_METHOD => [:user, :admin]
|
57
|
+
|
58
|
+
expected = { 'name' => 'buz', 'email' => 'foo' }
|
59
|
+
sanitized = student.sanitize_for_mass_assignment(expected, user)
|
60
|
+
assert_equal expected, sanitized
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_that_attributes_are_cached_and_not_created_second_time
|
64
|
+
student = Student.new
|
65
|
+
expected = { 'name' => 'buz', 'email' => 'foo' }
|
66
|
+
|
67
|
+
student.sanitize_for_mass_assignment(expected, [:user, :admin])
|
68
|
+
|
69
|
+
Student.expects(:attr_accessible).never
|
70
|
+
|
71
|
+
student.sanitize_for_mass_assignment(expected, [:user, :admin])
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_do_not_take_into_account_not_known_roles
|
75
|
+
student = Student.new
|
76
|
+
expected = { 'name' => 'foo', 'email' => 'bar' }
|
77
|
+
|
78
|
+
sanitized = student.sanitize_for_mass_assignment(
|
79
|
+
expected.merge('phone_number' => 'buz'),
|
80
|
+
[:user, :admin, :not_existent]
|
81
|
+
)
|
82
|
+
|
83
|
+
assert_equal expected, sanitized
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_do_not_fail_and_use_DEFAULT_if_wrong_data_type_was_passed_as_role
|
87
|
+
student = Student.new
|
88
|
+
expected = { 'phone_number' => 'buz' }
|
89
|
+
sanitized = student.sanitize_for_mass_assignment(expected.merge('email' => 'bar'), {wrong: 'data type'})
|
90
|
+
assert_equal expected, sanitized
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_it_not_fails_if_active_authorizer_is_empty
|
94
|
+
blank = Blank.new
|
95
|
+
expected = { 'name' => 'buz', 'email' => 'foo' }
|
96
|
+
sanitized = blank.sanitize_for_mass_assignment(expected, [:user, :admin])
|
97
|
+
assert_equal expected, sanitized
|
98
|
+
end
|
99
|
+
end
|
data/test/config.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Contact
|
2
|
+
extend ActiveModel::Naming
|
3
|
+
include ActiveModel::Conversion
|
4
|
+
|
5
|
+
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
|
6
|
+
|
7
|
+
def social
|
8
|
+
%w(twitter github)
|
9
|
+
end
|
10
|
+
|
11
|
+
def network
|
12
|
+
{:git => :github}
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
options.each { |name, value| send("#{name}=", value) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def pseudonyms
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def persisted?
|
24
|
+
id
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class User
|
2
|
+
include ActiveModel::MassAssignmentSecurity
|
3
|
+
attr_protected :admin
|
4
|
+
|
5
|
+
public :sanitize_for_mass_assignment
|
6
|
+
end
|
7
|
+
|
8
|
+
class SpecialUser
|
9
|
+
include ActiveModel::MassAssignmentSecurity
|
10
|
+
attr_accessible :name, :email, :as => :moderator
|
11
|
+
|
12
|
+
public :sanitize_for_mass_assignment
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person
|
16
|
+
include ActiveModel::MassAssignmentSecurity
|
17
|
+
attr_accessible :name, :email
|
18
|
+
attr_accessible :name, :email, :admin, :as => :admin
|
19
|
+
|
20
|
+
public :sanitize_for_mass_assignment
|
21
|
+
end
|
22
|
+
|
23
|
+
class Account
|
24
|
+
include ActiveModel::MassAssignmentSecurity
|
25
|
+
attr_accessible :name, :email, :as => [:default, :admin]
|
26
|
+
attr_accessible :admin, :as => :admin
|
27
|
+
|
28
|
+
public :sanitize_for_mass_assignment
|
29
|
+
end
|
30
|
+
|
31
|
+
class Firm
|
32
|
+
include ActiveModel::MassAssignmentSecurity
|
33
|
+
|
34
|
+
public :sanitize_for_mass_assignment
|
35
|
+
|
36
|
+
def self.attributes_protected_by_default
|
37
|
+
["type"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Task
|
42
|
+
include ActiveModel::MassAssignmentSecurity
|
43
|
+
attr_protected :starting
|
44
|
+
|
45
|
+
public :sanitize_for_mass_assignment
|
46
|
+
end
|
47
|
+
|
48
|
+
class LoosePerson
|
49
|
+
include ActiveModel::MassAssignmentSecurity
|
50
|
+
attr_protected :credit_rating, :administrator
|
51
|
+
attr_protected :credit_rating, :as => :admin
|
52
|
+
end
|
53
|
+
|
54
|
+
class LooseDescendant < LoosePerson
|
55
|
+
attr_protected :phone_number
|
56
|
+
end
|
57
|
+
|
58
|
+
class LooseDescendantSecond< LoosePerson
|
59
|
+
attr_protected :phone_number
|
60
|
+
attr_protected :name
|
61
|
+
end
|
62
|
+
|
63
|
+
class TightPerson
|
64
|
+
include ActiveModel::MassAssignmentSecurity
|
65
|
+
attr_accessible :name, :address
|
66
|
+
attr_accessible :name, :address, :admin, :as => :admin
|
67
|
+
|
68
|
+
def self.attributes_protected_by_default
|
69
|
+
["mobile_number"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TightDescendant < TightPerson
|
74
|
+
attr_accessible :phone_number
|
75
|
+
attr_accessible :super_powers, :as => :admin
|
76
|
+
end
|
77
|
+
|
78
|
+
class Student
|
79
|
+
include ActiveModel::MassAssignmentSecurity
|
80
|
+
|
81
|
+
attr_accessible :phone_number
|
82
|
+
attr_accessible :name, as: :admin
|
83
|
+
attr_accessible :email, as: :user
|
84
|
+
|
85
|
+
public :sanitize_for_mass_assignment
|
86
|
+
end
|
87
|
+
|
88
|
+
class Teacher
|
89
|
+
include ActiveModel::MassAssignmentSecurity
|
90
|
+
|
91
|
+
attr_protected :phone_number
|
92
|
+
attr_protected :name, as: :admin
|
93
|
+
attr_protected :email, as: :user
|
94
|
+
|
95
|
+
public :sanitize_for_mass_assignment
|
96
|
+
end
|
97
|
+
|
98
|
+
class Blank
|
99
|
+
include ActiveModel::MassAssignmentSecurity
|
100
|
+
|
101
|
+
public :sanitize_for_mass_assignment
|
102
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class ORM
|
2
|
+
include ActiveModel::Observing
|
3
|
+
|
4
|
+
def save
|
5
|
+
notify_observers :before_save
|
6
|
+
end
|
7
|
+
|
8
|
+
class Observer < ActiveModel::Observer
|
9
|
+
def before_save_invocations
|
10
|
+
@before_save_invocations ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_save(record)
|
14
|
+
before_save_invocations << record
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Widget < ORM; end
|
20
|
+
class Budget < ORM; end
|
21
|
+
class WidgetObserver < ORM::Observer; end
|
22
|
+
class BudgetObserver < ORM::Observer; end
|
23
|
+
class AuditTrail < ORM::Observer
|
24
|
+
observe :widget, :budget
|
25
|
+
end
|
26
|
+
|
27
|
+
ORM.instantiate_observers
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Person
|
2
|
+
include ActiveModel::Validations
|
3
|
+
extend ActiveModel::Translation
|
4
|
+
|
5
|
+
attr_accessor :title, :karma, :salary, :gender
|
6
|
+
|
7
|
+
def condition_is_true
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Person::Gender
|
13
|
+
extend ActiveModel::Translation
|
14
|
+
end
|
15
|
+
|
16
|
+
class Child < Person
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class PersonWithValidator
|
2
|
+
include ActiveModel::Validations
|
3
|
+
|
4
|
+
class PresenceValidator < ActiveModel::EachValidator
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class LikeValidator < ActiveModel::EachValidator
|
11
|
+
def initialize(options)
|
12
|
+
@with = options[:with]
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_each(record, attribute, value)
|
17
|
+
unless value[@with]
|
18
|
+
record.errors.add attribute, "does not appear to be like #{@with}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :title, :karma
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'models/topic'
|
2
|
+
|
3
|
+
class Reply < Topic
|
4
|
+
validate :errors_on_empty_content
|
5
|
+
validate :title_is_wrong_create, :on => :create
|
6
|
+
|
7
|
+
validate :check_empty_title
|
8
|
+
validate :check_content_mismatch, :on => :create
|
9
|
+
validate :check_wrong_update, :on => :update
|
10
|
+
|
11
|
+
def check_empty_title
|
12
|
+
errors[:title] << "is Empty" unless title && title.size > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors_on_empty_content
|
16
|
+
errors[:content] << "is Empty" unless content && content.size > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_content_mismatch
|
20
|
+
if title && content && content == "Mismatch"
|
21
|
+
errors[:title] << "is Content Mismatch"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def title_is_wrong_create
|
26
|
+
errors[:title] << "is Wrong Create" if title && title == "Wrong Create"
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_wrong_update
|
30
|
+
errors[:title] << "is Wrong Update" if title && title == "Wrong Update"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Topic
|
2
|
+
include ActiveModel::Validations
|
3
|
+
include ActiveModel::Validations::Callbacks
|
4
|
+
|
5
|
+
def self._validates_default_keys
|
6
|
+
super | [ :message ]
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :title, :author_name, :content, :approved
|
10
|
+
attr_accessor :after_validation_performed
|
11
|
+
|
12
|
+
after_validation :perform_after_validation
|
13
|
+
|
14
|
+
def initialize(attributes = {})
|
15
|
+
attributes.each do |key, value|
|
16
|
+
send "#{key}=", value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def condition_is_true
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def condition_is_true_but_its_not
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_after_validation
|
29
|
+
self.after_validation_performed = true
|
30
|
+
end
|
31
|
+
|
32
|
+
def my_validation
|
33
|
+
errors.add :title, "is missing" unless title
|
34
|
+
end
|
35
|
+
|
36
|
+
def my_validation_with_arg(attr)
|
37
|
+
errors.add attr, "is missing" unless send(attr)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/test/models/user.rb
ADDED
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mass_assignment_with_multiple_roles
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- saksmlz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.3
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mocha
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>'
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>'
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>'
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>'
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activemodel
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 3.2.3
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 3.2.3
|
78
|
+
description: This gem allows you to pass multiple roles into methods like save and
|
79
|
+
update_attributes
|
80
|
+
email:
|
81
|
+
- saksmlz@gmail.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .rvmrc
|
88
|
+
- .travis.yml
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- lib/mass_assignment_with_multiple_roles.rb
|
94
|
+
- lib/mass_assignment_with_multiple_roles/mass_assignment_security.rb
|
95
|
+
- lib/mass_assignment_with_multiple_roles/version.rb
|
96
|
+
- mass_assignment_with_multiple_roles.gemspec
|
97
|
+
- test/cases/helper.rb
|
98
|
+
- test/cases/mass_assignment_security_test.rb
|
99
|
+
- test/cases/mass_assignment_security_with_multiple_roles_test.rb
|
100
|
+
- test/config.rb
|
101
|
+
- test/models/administrator.rb
|
102
|
+
- test/models/automobile.rb
|
103
|
+
- test/models/blog_post.rb
|
104
|
+
- test/models/contact.rb
|
105
|
+
- test/models/custom_reader.rb
|
106
|
+
- test/models/helicopter.rb
|
107
|
+
- test/models/mass_assignment_specific.rb
|
108
|
+
- test/models/observers.rb
|
109
|
+
- test/models/person.rb
|
110
|
+
- test/models/person_with_validator.rb
|
111
|
+
- test/models/reply.rb
|
112
|
+
- test/models/sheep.rb
|
113
|
+
- test/models/topic.rb
|
114
|
+
- test/models/track_back.rb
|
115
|
+
- test/models/user.rb
|
116
|
+
- test/models/visitor.rb
|
117
|
+
homepage: http://github.com/saks/mass_assignment_with_multiple_roles
|
118
|
+
licenses: []
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
hash: -188045199
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
hash: -188045199
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 1.8.24
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: Allows to use intersection of attr_accessible if passing multiple role names
|
147
|
+
on save
|
148
|
+
test_files:
|
149
|
+
- test/cases/helper.rb
|
150
|
+
- test/cases/mass_assignment_security_test.rb
|
151
|
+
- test/cases/mass_assignment_security_with_multiple_roles_test.rb
|
152
|
+
- test/config.rb
|
153
|
+
- test/models/administrator.rb
|
154
|
+
- test/models/automobile.rb
|
155
|
+
- test/models/blog_post.rb
|
156
|
+
- test/models/contact.rb
|
157
|
+
- test/models/custom_reader.rb
|
158
|
+
- test/models/helicopter.rb
|
159
|
+
- test/models/mass_assignment_specific.rb
|
160
|
+
- test/models/observers.rb
|
161
|
+
- test/models/person.rb
|
162
|
+
- test/models/person_with_validator.rb
|
163
|
+
- test/models/reply.rb
|
164
|
+
- test/models/sheep.rb
|
165
|
+
- test/models/topic.rb
|
166
|
+
- test/models/track_back.rb
|
167
|
+
- test/models/user.rb
|
168
|
+
- test/models/visitor.rb
|