adva_rbac 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/NOTES +98 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/adva_rbac.gemspec +17 -0
- data/app/controllers/roles_controller.rb +28 -0
- data/app/helpers/roles_helper.rb +64 -0
- data/app/views/admin/sections/settings/_permissions.html.erb +16 -0
- data/app/views/roles/index.js.erb +3 -0
- data/config/initializers/base_controller.rb +4 -0
- data/config/initializers/rbac.rb +60 -0
- data/config/initializers/user.rb +80 -0
- data/db/migrate/20080402000006_create_role_tables.rb +13 -0
- data/db/migrate/20090720132900_migrate_roles_table_to_new_rbac.rb +15 -0
- data/lib/action_controller/guards_permissions.rb +77 -0
- data/lib/adva_rbac.rb +18 -0
- data/lib/adva_rbac/version.rb +3 -0
- data/lib/permission_map.rb +70 -0
- data/lib/rbac.rb +26 -0
- data/lib/rbac/acts_as_role_context.rb +44 -0
- data/lib/rbac/acts_as_role_subject.rb +65 -0
- data/lib/rbac/context.rb +85 -0
- data/lib/rbac/role.rb +10 -0
- data/lib/rbac/role_type.rb +73 -0
- data/lib/rbac/role_type/active_record.rb +47 -0
- data/lib/rbac/role_type/static.rb +144 -0
- data/lib/rbac/subject.rb +52 -0
- data/test/functional/roles_controller_test.rb +21 -0
- data/test/integration/user_rbac_test.rb +34 -0
- data/test/rbac/all.rb +3 -0
- data/test/rbac/database.rb +155 -0
- data/test/rbac/database.yml +3 -0
- data/test/rbac/implementation/active_record_test.rb +17 -0
- data/test/rbac/implementation/static_test.rb +14 -0
- data/test/rbac/static.rb +25 -0
- data/test/rbac/test_helper.rb +62 -0
- data/test/rbac/tests/acts_as_role_context.rb +37 -0
- data/test/rbac/tests/context.rb +35 -0
- data/test/rbac/tests/group.rb +40 -0
- data/test/rbac/tests/has_role.rb +126 -0
- data/test/rbac/tests/role_type.rb +110 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/helpers/roles_helper_test.rb +69 -0
- data/test/unit/models/rbac_context_test.rb +37 -0
- data/test/unit/models/rbac_user_test.rb +100 -0
- data/test/unit/models/role_test.rb +185 -0
- metadata +110 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Rbac
|
4
|
+
module RoleType
|
5
|
+
module ActiveRecord
|
6
|
+
class RoleTypeRelationship < ::ActiveRecord::Base
|
7
|
+
belongs_to :master, :class_name => "RoleType"
|
8
|
+
belongs_to :minion, :class_name => "RoleType"
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class RoleType < ::ActiveRecord::Base
|
13
|
+
include Rbac::RoleType
|
14
|
+
|
15
|
+
has_many :master_relationships, :foreign_key => 'master_id', :class_name => 'RoleTypeRelationship', :dependent => :destroy
|
16
|
+
has_many :minion_relationships, :foreign_key => 'minion_id', :class_name => 'RoleTypeRelationship', :dependent => :destroy
|
17
|
+
|
18
|
+
has_many :masters, :through => :minion_relationships
|
19
|
+
has_many :minions, :through => :master_relationships
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def build(name)
|
23
|
+
find_by_name(name.to_s) || raise(Rbac::UndefinedRoleType.new(name))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def requires_context?
|
28
|
+
!!attributes['requires_context']
|
29
|
+
end
|
30
|
+
|
31
|
+
def granted_to?(user, context = nil, options = {})
|
32
|
+
return super unless ['anonymous', 'user', 'author'].include?(name)
|
33
|
+
return false if options[:explicit]
|
34
|
+
|
35
|
+
case name
|
36
|
+
when 'anonymous'
|
37
|
+
true
|
38
|
+
when 'user'
|
39
|
+
user.try(:registered?)
|
40
|
+
when 'author'
|
41
|
+
context.respond_to?(:author) && context.author == user || super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Rbac
|
2
|
+
module RoleType
|
3
|
+
module Author
|
4
|
+
extend Rbac::RoleType
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def minions
|
8
|
+
[User]
|
9
|
+
end
|
10
|
+
|
11
|
+
def masters
|
12
|
+
[Moderator]
|
13
|
+
end
|
14
|
+
|
15
|
+
def granted_to?(user, context = nil, options = {})
|
16
|
+
options[:explicit] ? false : context.respond_to?(:author) && context.author == user || super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Static
|
22
|
+
mattr_accessor :role_types
|
23
|
+
self.role_types = [:editor, :superuser, :moderator, :author, :user, :anonymous]
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def build(name)
|
27
|
+
const_get(name.to_s.camelize)
|
28
|
+
end
|
29
|
+
|
30
|
+
def all
|
31
|
+
@role_types ||= role_types.map { |type| build(type) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Anonymous
|
36
|
+
extend Rbac::RoleType
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def requires_context?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def masters
|
44
|
+
[User]
|
45
|
+
end
|
46
|
+
|
47
|
+
def minions
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
|
51
|
+
def granted_to?(user, context = nil, options = {})
|
52
|
+
options[:explicit] ? false : true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module User
|
58
|
+
extend Rbac::RoleType
|
59
|
+
|
60
|
+
class << self
|
61
|
+
def requires_context?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def minions
|
66
|
+
[Anonymous]
|
67
|
+
end
|
68
|
+
|
69
|
+
def masters
|
70
|
+
[Editor, Author]
|
71
|
+
end
|
72
|
+
|
73
|
+
def granted_to?(user, context = nil, options = {})
|
74
|
+
options[:explicit] ? false : user.try(:registered?)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module Author
|
80
|
+
extend Rbac::RoleType
|
81
|
+
|
82
|
+
class << self
|
83
|
+
def minions
|
84
|
+
[User]
|
85
|
+
end
|
86
|
+
|
87
|
+
def masters
|
88
|
+
[Moderator]
|
89
|
+
end
|
90
|
+
|
91
|
+
def granted_to?(user, context = nil, options = {})
|
92
|
+
options[:explicit] ? false : context.respond_to?(:author) && context.author == user || super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module Moderator
|
98
|
+
extend Rbac::RoleType
|
99
|
+
|
100
|
+
class << self
|
101
|
+
def minions
|
102
|
+
[Author]
|
103
|
+
end
|
104
|
+
|
105
|
+
def masters
|
106
|
+
[Superuser]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module Superuser
|
112
|
+
extend Rbac::RoleType
|
113
|
+
|
114
|
+
class << self
|
115
|
+
def requires_context?
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
def minions
|
120
|
+
[Moderator, Editor]
|
121
|
+
end
|
122
|
+
|
123
|
+
def masters
|
124
|
+
[]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module Editor
|
130
|
+
extend Rbac::RoleType
|
131
|
+
|
132
|
+
class << self
|
133
|
+
def minions
|
134
|
+
[User]
|
135
|
+
end
|
136
|
+
|
137
|
+
def masters
|
138
|
+
[Superuser]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/rbac/subject.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Rbac
|
2
|
+
class Subject
|
3
|
+
class << self
|
4
|
+
def define_class(model, options)
|
5
|
+
Class.new(Base).tap do |klass|
|
6
|
+
model.const_set('RoleSubject', klass)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Base
|
12
|
+
attr_accessor :object
|
13
|
+
|
14
|
+
def initialize(object = nil)
|
15
|
+
self.object = object
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_permission?(action, context)
|
19
|
+
# puts "============== action = #{action}"
|
20
|
+
# puts "============== context = #{context}"
|
21
|
+
types = context.authorizing_role_types_for(action)
|
22
|
+
# puts "============== authorizing_role_types_for #{types}"
|
23
|
+
has_role?(types, context)
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_role?(types, context = nil)
|
27
|
+
Array(types).any? do |type|
|
28
|
+
type = Rbac::RoleType.build(type) unless type.respond_to?(:granted_to?)
|
29
|
+
type.granted_to?(self, context)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_explicit_role?(type, context = nil)
|
34
|
+
type = Rbac::RoleType.build(type) unless type.respond_to?(:granted_to?)
|
35
|
+
type.granted_to?(self, context, :explicit => true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
super || object == other # hmmmm ...
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to?(method)
|
43
|
+
object.respond_to?(method) || super
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method, *args, &block)
|
47
|
+
return object.send(method, *args, &block) if object.respond_to?(method)
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class RolesControllerTest < ActionController::TestCase
|
4
|
+
with_common :a_site, :a_user
|
5
|
+
|
6
|
+
test "is an BaseController" do
|
7
|
+
@controller.should be_kind_of(BaseController)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "GET to :index" do
|
11
|
+
action { get :index, { :user_id => @user.id } }
|
12
|
+
|
13
|
+
it_assigns :site, :user, :roles
|
14
|
+
it_renders_template :index, :format => :js
|
15
|
+
it_caches_the_page # FIXME should track user references, eh?
|
16
|
+
|
17
|
+
it "always includes the user role" do
|
18
|
+
assigns(:roles).map(&:name).should include('user')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper' ))
|
2
|
+
|
3
|
+
module IntegrationTests
|
4
|
+
class UserRbacTest < ActionController::IntegrationTest
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@site = use_site! 'site with pages'
|
8
|
+
@superuser = User.find_by_email('a-superuser@example.com')
|
9
|
+
end
|
10
|
+
|
11
|
+
test "Updating the user account does not remove users roles" do
|
12
|
+
login_as_superuser
|
13
|
+
visit_user_edit_form
|
14
|
+
fill_and_submit_user_edit_form
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_user_edit_form
|
18
|
+
visit "admin/sites/#{@site.id}/users/#{@superuser.id}/edit"
|
19
|
+
assert 'admin/users/edit'
|
20
|
+
end
|
21
|
+
|
22
|
+
def fill_and_submit_user_edit_form
|
23
|
+
assert @superuser.has_role?(:superuser)
|
24
|
+
|
25
|
+
fill_in 'user_first_name', :with => "the awesome"
|
26
|
+
fill_in 'user_last_name', :with => "superuser"
|
27
|
+
click_button 'Save'
|
28
|
+
|
29
|
+
@superuser.reload
|
30
|
+
assert @superuser.name == "the awesome superuser"
|
31
|
+
assert @superuser.has_role?(:superuser)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/test/rbac/all.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/test_helper")
|
2
|
+
|
3
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
4
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
5
|
+
|
6
|
+
ActiveRecord::Base.connection.create_table :users do |t|
|
7
|
+
t.string :name
|
8
|
+
t.boolean :anonymous
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.connection.create_table :sites do |t|
|
12
|
+
t.string :name
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::Base.connection.create_table :sections do |t|
|
16
|
+
t.string :title
|
17
|
+
t.text :permissions
|
18
|
+
t.references :site
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Base.connection.create_table :contents do |t|
|
22
|
+
t.references :section
|
23
|
+
t.references :author
|
24
|
+
t.string :title
|
25
|
+
t.text :permissions
|
26
|
+
end
|
27
|
+
|
28
|
+
ActiveRecord::Base.connection.create_table :roles do |t|
|
29
|
+
t.references :subject, :polymorphic => true
|
30
|
+
t.references :context, :polymorphic => true
|
31
|
+
t.string :name
|
32
|
+
end
|
33
|
+
|
34
|
+
ActiveRecord::Base.connection.create_table :groups do |t|
|
35
|
+
t.string :name
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveRecord::Base.connection.create_table :group_memberships do |t|
|
39
|
+
t.references :user
|
40
|
+
t.references :group
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecord::Base.connection.create_table :role_types do |t|
|
44
|
+
t.string :name
|
45
|
+
t.boolean :requires_context, :default => true
|
46
|
+
end
|
47
|
+
|
48
|
+
ActiveRecord::Base.connection.create_table :role_type_relationships do |t|
|
49
|
+
t.references :master
|
50
|
+
t.references :minion
|
51
|
+
end
|
52
|
+
|
53
|
+
class User < ActiveRecord::Base
|
54
|
+
class RoleSubject < Rbac::Subject::Base
|
55
|
+
def roles
|
56
|
+
(object.roles + object.groups.map(&:roles).flatten).uniq # should obviously happen in a single query
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
acts_as_role_subject
|
61
|
+
has_many :roles, :as => :subject, :class_name => 'Rbac::Role'
|
62
|
+
has_many :group_memberships
|
63
|
+
has_many :groups, :through => :group_memberships
|
64
|
+
|
65
|
+
def registered?
|
66
|
+
!new_record? && !anonymous?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class GroupMembership < ActiveRecord::Base
|
71
|
+
belongs_to :user
|
72
|
+
belongs_to :group
|
73
|
+
end
|
74
|
+
|
75
|
+
class Group < ActiveRecord::Base
|
76
|
+
acts_as_role_subject
|
77
|
+
|
78
|
+
has_many :group_memberships
|
79
|
+
has_many :members, :through => :group_memberships, :source => :user, :class_name => 'User'
|
80
|
+
has_many :roles, :as => :subject, :class_name => 'Rbac::Role'
|
81
|
+
end
|
82
|
+
|
83
|
+
class Site < ActiveRecord::Base
|
84
|
+
acts_as_role_context
|
85
|
+
end
|
86
|
+
|
87
|
+
class Section < ActiveRecord::Base
|
88
|
+
acts_as_role_context
|
89
|
+
|
90
|
+
belongs_to :site
|
91
|
+
|
92
|
+
def include?(other)
|
93
|
+
!!other
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Content < ActiveRecord::Base
|
98
|
+
acts_as_role_context :parent => :owner
|
99
|
+
|
100
|
+
belongs_to :section
|
101
|
+
belongs_to :author, :class_name => 'User'
|
102
|
+
|
103
|
+
def owner
|
104
|
+
section
|
105
|
+
end
|
106
|
+
|
107
|
+
def include?(other)
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
RoleType = Rbac::RoleType::ActiveRecord::RoleType
|
114
|
+
|
115
|
+
site = Site.create!(:name => 'a site')
|
116
|
+
another_site = Site.create!(:name => 'another site')
|
117
|
+
|
118
|
+
anonymous_type = RoleType.create!(:name => 'anonymous', :requires_context => false)
|
119
|
+
user_type = RoleType.create!(:name => 'user', :requires_context => false, :minions => [anonymous_type])
|
120
|
+
author_type = RoleType.create!(:name => 'author', :requires_context => true , :minions => [user_type])
|
121
|
+
moderator_type = RoleType.create!(:name => 'moderator', :requires_context => true , :minions => [author_type])
|
122
|
+
editor_type = RoleType.create!(:name => 'editor', :requires_context => true , :minions => [user_type])
|
123
|
+
superuser_type = RoleType.create!(:name => 'superuser', :requires_context => false, :minions => [moderator_type, editor_type])
|
124
|
+
pizzaboy_type = RoleType.create!(:name => 'pizzaboy', :requires_context => true)
|
125
|
+
|
126
|
+
superuser = User.create!(:name => 'superuser')
|
127
|
+
admin = User.create!(:name => 'site admin')
|
128
|
+
editor = User.create!(:name => 'editor')
|
129
|
+
moderator = User.create!(:name => 'moderator')
|
130
|
+
site_moderator = User.create!(:name => 'site moderator')
|
131
|
+
author = User.create!(:name => 'author')
|
132
|
+
user = User.create!(:name => 'user')
|
133
|
+
anonymous = User.create!(:name => 'anonymous', :anonymous => true)
|
134
|
+
site_designer = User.create!(:name => 'designer')
|
135
|
+
|
136
|
+
blog = Section.create!(:title => 'blog', :site => site)
|
137
|
+
content = Content.create!(:title => 'content', :section => blog, :author => author)
|
138
|
+
|
139
|
+
john = User.create!(:name => 'john')
|
140
|
+
paul = User.create!(:name => 'paul')
|
141
|
+
mick = User.create!(:name => 'mick')
|
142
|
+
keith = User.create!(:name => 'keith')
|
143
|
+
|
144
|
+
beatles = Group.create!(:name => 'beatles', :members => [john, paul])
|
145
|
+
stones = Group.create!(:name => 'stones', :members => [mick, keith])
|
146
|
+
|
147
|
+
beatles.roles.create!(:name => 'superuser')
|
148
|
+
stones.roles.create!(:name => 'pizzaboy')
|
149
|
+
editor.roles.create!(:name => 'editor')
|
150
|
+
superuser.roles.create!(:name => 'superuser')
|
151
|
+
admin.roles.create!(:name => 'admin', :context => site)
|
152
|
+
moderator.roles.create!(:name => 'moderator', :context => blog)
|
153
|
+
author.roles.create!(:name => 'author', :context => site)
|
154
|
+
site_moderator.roles.create!(:name => 'moderator', :context => site)
|
155
|
+
site_designer.roles.create!(:name => 'designer', :context => site)
|