mass_assignment_with_multiple_roles 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/saks/mass_assignment_with_multiple_roles.png)](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
|