ktopping_acl9 0.12.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.
- data/CHANGELOG.textile +46 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +903 -0
- data/Rakefile +40 -0
- data/TODO +42 -0
- data/VERSION.yml +5 -0
- data/lib/acl9/config.rb +11 -0
- data/lib/acl9/controller_extensions/dsl_base.rb +230 -0
- data/lib/acl9/controller_extensions/generators.rb +197 -0
- data/lib/acl9/controller_extensions.rb +85 -0
- data/lib/acl9/helpers.rb +42 -0
- data/lib/acl9/model_extensions/for_object.rb +59 -0
- data/lib/acl9/model_extensions/for_subject.rb +184 -0
- data/lib/acl9/model_extensions.rb +139 -0
- data/lib/acl9.rb +16 -0
- data/test/access_control_test.rb +338 -0
- data/test/dsl_base_test.rb +795 -0
- data/test/helpers_test.rb +134 -0
- data/test/roles_test.rb +355 -0
- data/test/support/controllers.rb +207 -0
- data/test/support/models.rb +59 -0
- data/test/support/schema.rb +92 -0
- data/test/test_helper.rb +31 -0
- metadata +119 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Acl9
|
2
|
+
module ModelExtensions
|
3
|
+
module ForObject
|
4
|
+
##
|
5
|
+
# Role check.
|
6
|
+
#
|
7
|
+
# @return [Boolean] Returns true if +subject+ has a role +role_name+ on this object.
|
8
|
+
#
|
9
|
+
# @param [Symbol,String] role_name Role name
|
10
|
+
# @param [Subject] subject Subject to add role for
|
11
|
+
# @see Acl9::ModelExtensions::Subject#has_role?
|
12
|
+
def accepts_role?(role_name, subject)
|
13
|
+
subject.has_role? role_name, self
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Add role on the object to specified subject.
|
18
|
+
#
|
19
|
+
# @param [Symbol,String] role_name Role name
|
20
|
+
# @param [Subject] subject Subject to add role for
|
21
|
+
# @see Acl9::ModelExtensions::Subject#has_role!
|
22
|
+
def accepts_role!(role_name, subject)
|
23
|
+
subject.has_role! role_name, self
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Free specified subject of a role on this object.
|
28
|
+
#
|
29
|
+
# @param [Symbol,String] role_name Role name
|
30
|
+
# @param [Subject] subject Subject to remove role from
|
31
|
+
# @see Acl9::ModelExtensions::Subject#has_no_role!
|
32
|
+
def accepts_no_role!(role_name, subject)
|
33
|
+
subject.has_no_role! role_name, self
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Are there any roles for the specified +subject+ on this object?
|
38
|
+
#
|
39
|
+
# @param [Subject] subject Subject to query roles
|
40
|
+
# @return [Boolean] Returns true if +subject+ has any roles on this object.
|
41
|
+
# @see Acl9::ModelExtensions::Subject#has_roles_for?
|
42
|
+
def accepts_roles_by?(subject)
|
43
|
+
subject.has_roles_for? self
|
44
|
+
end
|
45
|
+
|
46
|
+
alias :accepts_role_by? :accepts_roles_by?
|
47
|
+
|
48
|
+
##
|
49
|
+
# Which roles does +subject+ have on this object?
|
50
|
+
#
|
51
|
+
# @return [Array<Role>] Role instances, associated both with +subject+ and +object+
|
52
|
+
# @param [Subject] subject Subject to query roles
|
53
|
+
# @see Acl9::ModelExtensions::Subject#roles_for
|
54
|
+
def accepted_roles_by(subject)
|
55
|
+
subject.roles_for self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Acl9
|
2
|
+
module ModelExtensions
|
3
|
+
module ForSubject
|
4
|
+
##
|
5
|
+
# Role check.
|
6
|
+
#
|
7
|
+
# There is a global option, +Acl9.config[:protect_global_roles]+, which governs
|
8
|
+
# this method behavior.
|
9
|
+
#
|
10
|
+
# If protect_global_roles is +false+, an object role is automatically counted
|
11
|
+
# as global role. E.g.
|
12
|
+
#
|
13
|
+
# Acl9.config[:protect_global_roles] = false
|
14
|
+
# user.has_role!(:manager, @foo)
|
15
|
+
# user.has_role?(:manager, @foo) # => true
|
16
|
+
# user.has_role?(:manager) # => true
|
17
|
+
#
|
18
|
+
# In this case manager is anyone who "manages" at least one object.
|
19
|
+
#
|
20
|
+
# However, if protect_global_roles option set to +true+, you'll need to
|
21
|
+
# explicitly grant global role with same name.
|
22
|
+
#
|
23
|
+
# Acl9.config[:protect_global_roles] = true
|
24
|
+
# user.has_role!(:manager, @foo)
|
25
|
+
# user.has_role?(:manager) # => false
|
26
|
+
# user.has_role!(:manager)
|
27
|
+
# user.has_role?(:manager) # => true
|
28
|
+
#
|
29
|
+
# protect_global_roles option is +false+ by default as for now, but this
|
30
|
+
# may change in future!
|
31
|
+
#
|
32
|
+
# @return [Boolean] Whether +self+ has a role +role_name+ on +object+.
|
33
|
+
# @param [Symbol,String] role_name Role name
|
34
|
+
# @param [Object] object Object to query a role on
|
35
|
+
#
|
36
|
+
# @see Acl9::ModelExtensions::Object#accepts_role?
|
37
|
+
def has_role?(role_name, object = nil)
|
38
|
+
!! if object.nil? && !::Acl9.config[:protect_global_roles]
|
39
|
+
self.role_objects.find_by_name(role_name.to_s) ||
|
40
|
+
self.role_objects.member?(get_role(role_name, nil))
|
41
|
+
else
|
42
|
+
role = get_role(role_name, object)
|
43
|
+
role && self.role_objects.exists?(role.id)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Add specified role on +object+ to +self+.
|
49
|
+
#
|
50
|
+
# @param [Symbol,String] role_name Role name
|
51
|
+
# @param [Object] object Object to add a role for
|
52
|
+
# @see Acl9::ModelExtensions::Object#accepts_role!
|
53
|
+
def has_role!(role_name, object = nil)
|
54
|
+
role = get_role(role_name, object)
|
55
|
+
|
56
|
+
if role.nil?
|
57
|
+
role_attrs = case object
|
58
|
+
when Class then { :authorizable_type => object.to_s }
|
59
|
+
when nil then {}
|
60
|
+
else { :authorizable => object }
|
61
|
+
end.merge( { :name => role_name.to_s })
|
62
|
+
|
63
|
+
role = self._auth_role_class.create(role_attrs)
|
64
|
+
end
|
65
|
+
|
66
|
+
self.role_objects << role if role && !self.role_objects.exists?(role.id)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Free +self+ from a specified role on +object+.
|
71
|
+
#
|
72
|
+
# @param [Symbol,String] role_name Role name
|
73
|
+
# @param [Object] object Object to remove a role on
|
74
|
+
# @see Acl9::ModelExtensions::Object#accepts_no_role!
|
75
|
+
def has_no_role!(role_name, object = nil)
|
76
|
+
delete_role(get_role(role_name, object))
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Are there any roles for +self+ on +object+?
|
81
|
+
#
|
82
|
+
# @param [Object] object Object to query roles
|
83
|
+
# @return [Boolean] Returns true if +self+ has any roles on +object+.
|
84
|
+
# @see Acl9::ModelExtensions::Object#accepts_roles_by?
|
85
|
+
def has_roles_for?(object)
|
86
|
+
!!self.role_objects.detect(&role_selecting_lambda(object))
|
87
|
+
end
|
88
|
+
|
89
|
+
alias :has_role_for? :has_roles_for?
|
90
|
+
|
91
|
+
##
|
92
|
+
# Which roles does +self+ have on +object+?
|
93
|
+
#
|
94
|
+
# @return [Array<Role>] Role instances, associated both with +self+ and +object+
|
95
|
+
# @param [Object] object Object to query roles
|
96
|
+
# @see Acl9::ModelExtensions::Object#accepted_roles_by
|
97
|
+
# @example
|
98
|
+
# user = User.find(...)
|
99
|
+
# product = Product.find(...)
|
100
|
+
#
|
101
|
+
# user.roles_for(product).map(&:name).sort #=> role names in alphabetical order
|
102
|
+
def roles_for(object)
|
103
|
+
self.role_objects.select(&role_selecting_lambda(object))
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Unassign any roles on +object+ from +self+.
|
108
|
+
#
|
109
|
+
# @param [Object,nil] object Object to unassign roles for. +nil+ means unassign global roles.
|
110
|
+
def has_no_roles_for!(object = nil)
|
111
|
+
roles_for(object).each { |role| delete_role(role) }
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Unassign all roles from +self+.
|
116
|
+
def has_no_roles!
|
117
|
+
# for some reason simple
|
118
|
+
#
|
119
|
+
# self.roles.each { |role| delete_role(role) }
|
120
|
+
#
|
121
|
+
# doesn't work. seems like a bug in ActiveRecord
|
122
|
+
self.role_objects.map(&:id).each do |role_id|
|
123
|
+
delete_role self._auth_role_class.find(role_id)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def role_selecting_lambda(object)
|
130
|
+
case object
|
131
|
+
when Class
|
132
|
+
lambda { |role| role.authorizable_type == object.to_s }
|
133
|
+
when nil
|
134
|
+
lambda { |role| role.authorizable.nil? }
|
135
|
+
else
|
136
|
+
lambda do |role|
|
137
|
+
role.authorizable_type == object.class.base_class.to_s && role.authorizable == object
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_role(role_name, object)
|
143
|
+
role_name = role_name.to_s
|
144
|
+
|
145
|
+
cond = case object
|
146
|
+
when Class
|
147
|
+
[ 'name = ? and authorizable_type = ? and authorizable_id IS NULL', role_name, object.to_s ]
|
148
|
+
when nil
|
149
|
+
[ 'name = ? and authorizable_type IS NULL and authorizable_id IS NULL', role_name ]
|
150
|
+
else
|
151
|
+
[
|
152
|
+
'name = ? and authorizable_type = ? and authorizable_id = ?',
|
153
|
+
role_name, object.class.base_class.to_s, object.id
|
154
|
+
]
|
155
|
+
end
|
156
|
+
|
157
|
+
self._auth_role_class.first :conditions => cond
|
158
|
+
end
|
159
|
+
|
160
|
+
def delete_role(role)
|
161
|
+
if role
|
162
|
+
self.role_objects.delete role
|
163
|
+
|
164
|
+
role.destroy if role.send(self._auth_subject_class_name.demodulize.tableize).empty?
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
def _auth_role_class
|
171
|
+
self.class._auth_role_class_name.constantize
|
172
|
+
end
|
173
|
+
|
174
|
+
def _auth_role_assoc
|
175
|
+
self.class._auth_role_assoc_name
|
176
|
+
end
|
177
|
+
|
178
|
+
def role_objects
|
179
|
+
send(self._auth_role_assoc)
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'model_extensions', 'for_subject')
|
2
|
+
require File.join(File.dirname(__FILE__), 'model_extensions', 'for_object')
|
3
|
+
|
4
|
+
module Acl9
|
5
|
+
module ModelExtensions #:nodoc:
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Add #has_role? and other role methods to the class.
|
12
|
+
# Makes a class a auth. subject class.
|
13
|
+
#
|
14
|
+
# @param [Hash] options the options for tuning
|
15
|
+
# @option options [String] :role_class_name (Acl9::config[:default_role_class_name])
|
16
|
+
# Class name of the role class (e.g. 'AccountRole')
|
17
|
+
# @option options [String] :join_table_name (Acl9::config[:default_join_table_name])
|
18
|
+
# Join table name (e.g. 'accounts_account_roles')
|
19
|
+
# @option options [String] :association_name (Acl9::config[:default_association_name])
|
20
|
+
# Association name (e.g. ':roles')
|
21
|
+
# @example
|
22
|
+
# class User < ActiveRecord::Base
|
23
|
+
# acts_as_authorization_subject
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# user = User.new
|
27
|
+
# user.roles #=> returns Role objects, associated with the user
|
28
|
+
# user.has_role!(...)
|
29
|
+
# user.has_no_role!(...)
|
30
|
+
#
|
31
|
+
# # other functions from Acl9::ModelExtensions::Subject are made available
|
32
|
+
#
|
33
|
+
# @see Acl9::ModelExtensions::Subject
|
34
|
+
#
|
35
|
+
def acts_as_authorization_subject(options = {})
|
36
|
+
assoc = options[:association_name] || Acl9::config[:default_association_name]
|
37
|
+
role = options[:role_class_name] || Acl9::config[:default_role_class_name]
|
38
|
+
join_table = options[:join_table_name] || Acl9::config[:default_join_table_name] ||
|
39
|
+
join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(role))
|
40
|
+
|
41
|
+
has_and_belongs_to_many assoc, :class_name => role, :join_table => join_table
|
42
|
+
|
43
|
+
cattr_accessor :_auth_role_class_name, :_auth_subject_class_name,
|
44
|
+
:_auth_role_assoc_name
|
45
|
+
|
46
|
+
self._auth_role_class_name = role
|
47
|
+
self._auth_subject_class_name = self.to_s
|
48
|
+
self._auth_role_assoc_name = assoc
|
49
|
+
|
50
|
+
include Acl9::ModelExtensions::ForSubject
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add role query and set methods to the class (making it an auth object class).
|
54
|
+
#
|
55
|
+
# @param [Hash] options the options for tuning
|
56
|
+
# @option options [String] :subject_class_name (Acl9::config[:default_subject_class_name])
|
57
|
+
# Subject class name (e.g. 'User', or 'Account)
|
58
|
+
# @option options [String] :role_class_name (Acl9::config[:default_role_class_name])
|
59
|
+
# Role class name (e.g. 'AccountRole')
|
60
|
+
# @example
|
61
|
+
# class Product < ActiveRecord::Base
|
62
|
+
# acts_as_authorization_object
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# product = Product.new
|
66
|
+
# product.accepted_roles #=> returns Role objects, associated with the product
|
67
|
+
# product.users #=> returns User objects, associated with the product
|
68
|
+
# product.accepts_role!(...)
|
69
|
+
# product.accepts_no_role!(...)
|
70
|
+
# # other functions from Acl9::ModelExtensions::Object are made available
|
71
|
+
#
|
72
|
+
# @see Acl9::ModelExtensions::Object
|
73
|
+
#
|
74
|
+
def acts_as_authorization_object(options = {})
|
75
|
+
subject = options[:subject_class_name] || Acl9::config[:default_subject_class_name]
|
76
|
+
subj_table = subject.constantize.table_name
|
77
|
+
subj_col = subject.underscore
|
78
|
+
|
79
|
+
role = options[:role_class_name] || Acl9::config[:default_role_class_name]
|
80
|
+
role_table = role.constantize.table_name
|
81
|
+
|
82
|
+
sql_tables = <<-EOS
|
83
|
+
FROM #{subj_table}
|
84
|
+
INNER JOIN #{role_table}_#{subj_table} ON #{subj_col}_id = #{subj_table}.id
|
85
|
+
INNER JOIN #{role_table} ON #{role_table}.id = #{role.underscore}_id
|
86
|
+
EOS
|
87
|
+
|
88
|
+
sql_where = <<-'EOS'
|
89
|
+
WHERE authorizable_type = '#{self.class.base_class.to_s}'
|
90
|
+
AND authorizable_id = #{column_for_attribute(self.class.primary_key).text? ? "'#{id}'": id}
|
91
|
+
EOS
|
92
|
+
|
93
|
+
has_many :accepted_roles, :as => :authorizable, :class_name => role, :dependent => :destroy
|
94
|
+
|
95
|
+
has_many :"#{subj_table}",
|
96
|
+
:finder_sql => ("SELECT DISTINCT #{subj_table}.*" + sql_tables + sql_where),
|
97
|
+
:counter_sql => ("SELECT COUNT(DISTINCT #{subj_table}.id)" + sql_tables + sql_where),
|
98
|
+
:readonly => true
|
99
|
+
|
100
|
+
include Acl9::ModelExtensions::ForObject
|
101
|
+
end
|
102
|
+
|
103
|
+
# Make a class an auth role class.
|
104
|
+
#
|
105
|
+
# You'll probably never create or use objects of this class directly.
|
106
|
+
# Various auth. subject and object methods will do that for you
|
107
|
+
# internally.
|
108
|
+
#
|
109
|
+
# @param [Hash] options the options for tuning
|
110
|
+
# @option options [String] :subject_class_name (Acl9::config[:default_subject_class_name])
|
111
|
+
# Subject class name (e.g. 'User', or 'Account)
|
112
|
+
# @option options [String] :join_table_name (Acl9::config[:default_join_table_name])
|
113
|
+
# Join table name (e.g. 'accounts_account_roles')
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# class Role < ActiveRecord::Base
|
117
|
+
# acts_as_authorization_role
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# @see Acl9::ModelExtensions::Subject#has_role!
|
121
|
+
# @see Acl9::ModelExtensions::Subject#has_role?
|
122
|
+
# @see Acl9::ModelExtensions::Subject#has_no_role!
|
123
|
+
# @see Acl9::ModelExtensions::Object#accepts_role!
|
124
|
+
# @see Acl9::ModelExtensions::Object#accepts_role?
|
125
|
+
# @see Acl9::ModelExtensions::Object#accepts_no_role!
|
126
|
+
def acts_as_authorization_role(options = {})
|
127
|
+
subject = options[:subject_class_name] || Acl9::config[:default_subject_class_name]
|
128
|
+
join_table = options[:join_table_name] || Acl9::config[:default_join_table_name] ||
|
129
|
+
join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(subject))
|
130
|
+
|
131
|
+
has_and_belongs_to_many subject.demodulize.tableize.to_sym,
|
132
|
+
:class_name => subject,
|
133
|
+
:join_table => join_table
|
134
|
+
|
135
|
+
belongs_to :authorizable, :polymorphic => true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/acl9.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'config')
|
2
|
+
|
3
|
+
if defined? ActiveRecord::Base
|
4
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'model_extensions')
|
5
|
+
|
6
|
+
ActiveRecord::Base.send(:include, Acl9::ModelExtensions)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
if defined? ActionController::Base
|
11
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'controller_extensions')
|
12
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'helpers')
|
13
|
+
|
14
|
+
ActionController::Base.send(:include, Acl9::ControllerExtensions)
|
15
|
+
Acl9Helpers = Acl9::Helpers unless defined?(Acl9Helpers)
|
16
|
+
end
|