popel-active_acl_plus 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +504 -0
- data/README.rdoc +317 -0
- data/VERSION.yml +4 -0
- data/lib/active_acl.rb +32 -0
- data/lib/active_acl/acts_as_access_group.rb +57 -0
- data/lib/active_acl/acts_as_access_object.rb +114 -0
- data/lib/active_acl/base.rb +29 -0
- data/lib/active_acl/cache/memcache_adapter.rb +46 -0
- data/lib/active_acl/cache/no_cache_adapter.rb +22 -0
- data/lib/active_acl/db/active_record_adapter.rb +15 -0
- data/lib/active_acl/db/mysql_adapter.rb +29 -0
- data/lib/active_acl/grant.rb +53 -0
- data/lib/active_acl/handler/nested_set.rb +33 -0
- data/lib/active_acl/handler/object_handler.rb +253 -0
- data/lib/active_acl/load_controller_actions.rb +77 -0
- data/lib/active_acl/load_files_from.rb +21 -0
- data/lib/active_acl/options.rb +37 -0
- data/lib/active_acl/privilege_const_set.rb +22 -0
- metadata +342 -0
data/README.rdoc
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
=Active Access Control Lists Plus (ActiveAclPlus)
|
2
|
+
The ActiveAclPlus plugin implements a flexible, fast and easy to use generic access control system.
|
3
|
+
|
4
|
+
==License
|
5
|
+
ActiveAclPlus is released under the LGPL[http://www.opensource.org/licenses/lgpl-license.php]
|
6
|
+
(Gnu Lesser General Public License) - see the included LICENSE file, too.
|
7
|
+
|
8
|
+
==Features
|
9
|
+
* ease of use - uses polymorphic collections for associations.
|
10
|
+
* advanced design - uses SQL nested sets for inheritance, thus only needs a single DB query to decide on a permission request.
|
11
|
+
* scalable - there are no real benchmarks yet. But the system design is based on http://phpgacl.sourceforge.net, adding object orientation, polymorphism and two levels of caching. PhpGacl claims "A real-world working version with many added layers of complexity supports over 60,000 Accounts, 200 Groups and 300 ACO's." Tests on my dev notebook show 10 - 30 times performance improvements compared to active_rbac.
|
12
|
+
* caching - uses instance caching and optionally stores permission results in memcached using timeouts.
|
13
|
+
* flexible - grants simple (2D, like <code>current_user.has_permission?(User::LOGIN)</code>) and object level (3D, like <code>admin.has_permission?(Forum::ADMIN, :on => team_forum)</code>) permissions. Can assign and request permissions to and from every ActiveRecord model. No "hardcoded" permissions - all permissions can be assigned at runtime.
|
14
|
+
* grouping - permissions can be inherited at target and requester side through groups. Every model implementing an SQL nested set tree may be used as a group.
|
15
|
+
* ControllerAction model loader: It maps controller actions to the DB so they can be used in permission assignment. The access object is available via calling <code>current_action</code> on the controller.
|
16
|
+
* exchangeable DB interface: ActiveRecord and direct MySQL adapter available
|
17
|
+
* supports namespaced models and single table inheritance (STI)
|
18
|
+
|
19
|
+
==Limitations
|
20
|
+
* At present only one grouping type per model is supported. This could be changed on request but I don't see a use case for it yet.
|
21
|
+
* The DBMS has to support subselects. So PostgreSQL, Sqllite and MySQL 5 work, but MySQL 4 does not.
|
22
|
+
|
23
|
+
|
24
|
+
==Prerequisites/Installation
|
25
|
+
ActiveAclPlus uses the has_many_polymorphs[http://www.agilewebdevelopment.com/plugins/restful_authentication] plugin. Make shure you've got a recent version, old versions have bugs affecting active_acl_plus.
|
26
|
+
!!Be sure you have the paches for 2.1, see: http://rubyforge.org/forum/forum.php?thread_id=26041&forum_id=16450
|
27
|
+
|
28
|
+
./script/plugin install git://github.com/popel/active_acl_plus.git
|
29
|
+
|
30
|
+
or
|
31
|
+
|
32
|
+
sudo gem install activeaclplus
|
33
|
+
|
34
|
+
or from a github repository.
|
35
|
+
|
36
|
+
If you want to use grouped access objects you should install some kind of nested_set plugin
|
37
|
+
(awesome_nested_set,better_nested_set,...).
|
38
|
+
==Short summary
|
39
|
+
The ActiveAclPlus system consists of access objects, which can be organized by access groups,
|
40
|
+
that request privileges on each other. Allowing or denying access to a privilege is controlled by
|
41
|
+
ACL (access control list entry) objects. Access objects and access groups can be instances of arbitrary
|
42
|
+
ActiveRecord model classes enhanced by acts_as_access_object and acts_as_access_group. They are associated
|
43
|
+
to ACL entries via polymorphic associations.
|
44
|
+
|
45
|
+
===Access objects
|
46
|
+
These are basically requesters and targets in the permission system, as for example a User or a Forum model object.
|
47
|
+
In this case a user could act as a requester ("do I have privilege Y?") or target ("does access object X have
|
48
|
+
privilege Y on me?"). A Forum would most certainly be only used as a target, but all access objects can theoretically
|
49
|
+
be used as both requesters and targets. Access objects use the acts_as_access_object macro inside their definition.
|
50
|
+
This registers the model with the ACL system and enhances it with methods like has_privilege?.
|
51
|
+
|
52
|
+
See: ActiveAcl::Acts::AccessObject::ClassMethods, ActiveAcl::Acts::AccessObject::InstanceMethods,
|
53
|
+
ActiveAcl::Acts::AccessObject::SingletonMethods, ActiveAcl::Acts::Grant
|
54
|
+
|
55
|
+
|
56
|
+
Every access object class can specify an association that is used as the "grouping type" to it. So User may declare
|
57
|
+
has_and_belongs_to_many :user_groups and use acts_as_access_object :grouped_by => :user_groups. You can use
|
58
|
+
a has_and_belongs_to_many or a belongs_to association for this. Common mapping attributes
|
59
|
+
(:join_table, :foreign_key etc.) are supported.
|
60
|
+
|
61
|
+
===Access groups
|
62
|
+
The access group model needs to implement a tree with nested set semantics having a "left" and "right"
|
63
|
+
column, e.g. by using the built-in acts_as_nested_set or the much more recommended acts_as_betted_nested_set
|
64
|
+
plugin. Groups are declared with acts_as_access_group.
|
65
|
+
|
66
|
+
Groups may be used to specify inheritance hierarchies for permissions. So you could have a 'registered users'
|
67
|
+
group as a subgroup of the 'users' group and assign the privilege to log in to this group via an ACL entry.
|
68
|
+
Every user belonging to this group will now be granted the privilege to log in. Then you could add a subgroup
|
69
|
+
to registerd users, 'banned users', and deny the log in privilege for this group. Every user added to this
|
70
|
+
group would now be unable to log in, regardless of beeing in 'registered users' or not, as 'banned users' would
|
71
|
+
override the permission settings of 'registered users'.
|
72
|
+
|
73
|
+
===Privileges
|
74
|
+
A privilege object is an object for the thing we wish to define a permission for. So User::LOGIN could be a
|
75
|
+
privilege object for checking a users permission to log in, while Forum::ADMIN might define administration rights on a forum.
|
76
|
+
|
77
|
+
A privilege object itself is little more than it’s name and id and is usually bound to a constant inside the
|
78
|
+
application, as it is not expected to change at runtime. Privileges are usually created by the developer in
|
79
|
+
the source code and not in the admin frontend, as creating new privilege objects that have no meaning (by code
|
80
|
+
that checks for them) would be pointless.
|
81
|
+
|
82
|
+
===Access Control List (ACL) entries
|
83
|
+
ACL entries are the glue between all these objects, defining which requesters and requester groups have access
|
84
|
+
to which privileges, optionally defining target objects and target groups as well. ACL entries are organized by
|
85
|
+
ACL sections, for better overview in the admin screens.
|
86
|
+
|
87
|
+
==Usage
|
88
|
+
===In short
|
89
|
+
"No access defined" for a privilege evaluates to "deny". This may be overriden by an explicit "allow" or "deny".
|
90
|
+
Privileges are inherited in requestor and target groups, this means you can override them in subgroups again.
|
91
|
+
Privileges directly assigned to an object always supercede those assigned to groups.
|
92
|
+
|
93
|
+
===Simple (2D) permissions
|
94
|
+
We want all registered users to be able to log in. We create the User model, the UserGroup model and the
|
95
|
+
User::LOGIN privilege object as described above. Then we create a new ACL entry, set 'allow' to true, add
|
96
|
+
the "registered users" group as requester group, User::LOGIN as privilege and we are done. Every user assigned
|
97
|
+
to "registered users" or a subgroup of it will now be granted access by calling <code>my_user.has_privilege?(User::LOGIN)</code>.
|
98
|
+
|
99
|
+
====Simple permissions example
|
100
|
+
|
101
|
+
class UserGroup < ActiveRecord::Base
|
102
|
+
acts_as_nested_set
|
103
|
+
acts_as_access_group
|
104
|
+
has_and_belongs_to_many :users
|
105
|
+
end
|
106
|
+
|
107
|
+
class User < ActiveRecord::Base
|
108
|
+
has_and_belongs_to_many :user_groups
|
109
|
+
acts_as_access_object :grouped_by => :user_groups
|
110
|
+
privilege_const_set('LOGIN')
|
111
|
+
end
|
112
|
+
|
113
|
+
# assume 'registered_users' exists and users 'john' and 'dr_evil' are members of it but 'anonymous' is not.
|
114
|
+
registered_users = UserGroup.find_by_name('registered_users')
|
115
|
+
|
116
|
+
the hard way:
|
117
|
+
acl = ActiveAcl::Acl.create :section => ActiveAcl::AclSection.create(:description => 'users')
|
118
|
+
|
119
|
+
acl.allow = true # true is default
|
120
|
+
acl.privileges << User::LOGIN
|
121
|
+
acl.iname="login"
|
122
|
+
acl.save
|
123
|
+
|
124
|
+
acl.requester_groups << registered_users
|
125
|
+
|
126
|
+
or much easier:
|
127
|
+
|
128
|
+
registered_users.grant_privilege!(User::LOGIN,:section_name => 'users',:acl_name => 'login')
|
129
|
+
|
130
|
+
john.has_privilege?(User::LOGIN) #=> true
|
131
|
+
dr_evil.has_privilege?(User::LOGIN) #=> true
|
132
|
+
|
133
|
+
anonymous.has_privilege?(User::LOGIN) #=> false
|
134
|
+
|
135
|
+
===Overriding permissions
|
136
|
+
We want to ban specific users from our site. We create another ACL entry, assign the User::LOGIN privilege
|
137
|
+
object, set 'allow' to false and then assign these users as requesters to the ACL entry. The direct
|
138
|
+
permission assignment on the objects overrides the 'allow login' ACL entry from above.
|
139
|
+
|
140
|
+
====Overriding permissions example
|
141
|
+
|
142
|
+
ban_users = ActiveAcl::Acl.create :section => ActiveAcl::AclSection.find_by_description('users')
|
143
|
+
|
144
|
+
ban_users.allow = false
|
145
|
+
ban_users.privileges << User::LOGIN
|
146
|
+
ban_users.requesters << dr_evil
|
147
|
+
|
148
|
+
ban_users.save
|
149
|
+
|
150
|
+
john.has_privilege?(User::LOGIN) #=> true
|
151
|
+
dr_evil.has_privilege?(User::LOGIN) #=> false
|
152
|
+
|
153
|
+
===Object level (3D) permissions
|
154
|
+
We want to assign forum permissions. We have several privileges (Forum::ADMIN, Forum::READ, Forum::POST etc.),
|
155
|
+
the afore mentioned User and UserGroup models as well as a Forum and a Category model for grouping the forums.
|
156
|
+
|
157
|
+
If we want to check if a certain user may read in a certain forum, it is not sufficient to check
|
158
|
+
<code>test_user.has_privilege?(Forum::READ)</code> as the target object - in this case a forum - is needed
|
159
|
+
to make a decision. The code to do the check is like <code>test_user.has_privilege?(Forum::READ, :on => teamforum)</code>.
|
160
|
+
|
161
|
+
To make this work you create a new ACL entry, add Forum::POST and Forum::READ as privileges, set 'allow' to
|
162
|
+
true, add the registered users group as a requester group and the public forums category as a target group
|
163
|
+
to the acl. Now every user belonging to the registered users group or a subgroup of it gains post and read
|
164
|
+
privileges on all forums of the public forums category or a subcategory of it.
|
165
|
+
|
166
|
+
====Object level permissions example
|
167
|
+
|
168
|
+
# Assuming setup as in the above examples
|
169
|
+
|
170
|
+
class Category < ActiveRecord::Base
|
171
|
+
acts_as_nested_set
|
172
|
+
acts_as_access_group
|
173
|
+
has_many :forums
|
174
|
+
end
|
175
|
+
|
176
|
+
class Forum < ActiveRecord::Base
|
177
|
+
belongs_to :category
|
178
|
+
acts_as_access_object :grouped_by => :category
|
179
|
+
privilege_const_set 'READ' => 'read postings in forum',
|
180
|
+
'POST' => 'reply to threads in a forum'
|
181
|
+
end
|
182
|
+
|
183
|
+
# assume there is a forum 'speakers corner' assigned to the category 'public'.
|
184
|
+
|
185
|
+
acl = ActiveAcl::Acl.create :section => ActiveAcl::AclSection.create(:description => 'forum')
|
186
|
+
|
187
|
+
acl.allow = true
|
188
|
+
acl.requester_groups << registered_users
|
189
|
+
acl.target_groups << Category.find_by_name('public')
|
190
|
+
|
191
|
+
acl.privileges << Forum::READ
|
192
|
+
acl.privileges << Forum::POST
|
193
|
+
|
194
|
+
acl.save
|
195
|
+
|
196
|
+
speakers = Forum.find_by_name('speakers corner')
|
197
|
+
|
198
|
+
john.has_privilege?(Forum::READ, :on => speakers) #=> true
|
199
|
+
john.has_privilege?(Forum::POST, :on => speakers) #=> true
|
200
|
+
anonymous.has_privilege?(Forum::READ, :on => speakers) #=> false
|
201
|
+
|
202
|
+
==CAUTION
|
203
|
+
Do not create ACL entries that are on different branches of the inheritance hierarchy and have
|
204
|
+
allow/deny set differently on the same privilege objects. This way it's impossible to tell which
|
205
|
+
permission should take precedence. At present this is by creation date, later entries superceding older
|
206
|
+
ones, but this is most certainly not what you want.
|
207
|
+
|
208
|
+
==Controller Actions
|
209
|
+
Defining permissions on controller actions (like "may user x execute AdminController.list ?") is quite a
|
210
|
+
common case but we are facing a problem here: Controller actions have no corresponding DB models so
|
211
|
+
permissions on them can't be easily defined.
|
212
|
+
|
213
|
+
ActiveAclPlus solves this by adding a ControllerAction and ControllerGroup model. For every public
|
214
|
+
controller method (=action) there is one ControllerAction object in the DB.
|
215
|
+
|
216
|
+
On application startup, the plugin loader checks all controller files in app/controllers and loads
|
217
|
+
or creates a ControllerAction object for every action it finds. These objects get cached in a hash
|
218
|
+
in the ActiveAclPlus module. Every controller now has a method <code>current_action</code> that looks
|
219
|
+
up and returns the access object for the current action, so it can be used for access checks like
|
220
|
+
<code>current_user.has_privilege?(ActiveAcl::ControllerAction::EXECUTE, :on => current_action)</code>.
|
221
|
+
|
222
|
+
This works nicely for before_filters with authorization checks.
|
223
|
+
|
224
|
+
A word on the load mechanism: If the action has no corresponding DB entry (it's looked up on method creation
|
225
|
+
by controller and action name) the loader searches for a controller group with the same name as the
|
226
|
+
controller. If it is found, the action is created and assigned to this group. Else the controller group
|
227
|
+
is created as a subgroup to the "unassigned controller actions" group (this name can be changed in the
|
228
|
+
options) and the unassigned action is added to the controller group.
|
229
|
+
|
230
|
+
So you are free on how to organize your controllers. Maybe create an admin group and a public group and
|
231
|
+
move controllers as a subgroup inside them?
|
232
|
+
|
233
|
+
==Caching
|
234
|
+
The plugin provides two levels of caching. The instance cache is a hash inside the access object. The object
|
235
|
+
first tries to serve a permission request from the instance cache. If it is not found and a simple permission
|
236
|
+
is requested, the query fetches all simple permissions (2D) of the object and puts them in the instance cache. The
|
237
|
+
reason for this is that there is no noticable speed penalty in fetching all 2D permissions at once compared to
|
238
|
+
fetching only one, so this will save time and DB IO later on. Complex 3D queries are fetched independently
|
239
|
+
and also saved to the instance cache. The instance cache lives inside the access object, so it has it's
|
240
|
+
lifetime, too - which in rails usually is no more than a single request.
|
241
|
+
|
242
|
+
The second level cache tries to overcome this limitation by putting the instance cache of an access object
|
243
|
+
in an external cache. It tries to get the instance cache from there if it is not set, and sets it if it was
|
244
|
+
changed. The only real implementation for now is with the memcache daemon. The second level cache uses a timeout
|
245
|
+
(which can be defined in the options) to expire the cached permissions.
|
246
|
+
|
247
|
+
Instance and second level cache can be expired explicitly by calling <code>active_acl_clear_cache!</code>
|
248
|
+
on the access object. Calling <code>reload</code> on the object also purges the caches.
|
249
|
+
|
250
|
+
See ActiveAcl::Cache::MemcacheAdapter on how to set it up.
|
251
|
+
|
252
|
+
|
253
|
+
==Preloader
|
254
|
+
The plugin includes a <code>load_files_from filenames</code> function. It can be used to preload source files
|
255
|
+
(and therefore the classes in it) from an application path and should be used from environment.rb.
|
256
|
+
|
257
|
+
load_files_from("#{RAILS_ROOT}/app/controllers/**/[^.]*.rb")
|
258
|
+
load_files_from("#{RAILS_ROOT}/app/models/**/[^.]*.rb")
|
259
|
+
|
260
|
+
will load all models and controllers inside these folders and their subfolders. This way you can be shure they
|
261
|
+
are registered with the ACL system at rails boot time - else they will be registered when they are called for
|
262
|
+
the first time. This means that new controllers will not show up in the admin screens until they were accessed
|
263
|
+
if not using the preloader.
|
264
|
+
|
265
|
+
==Options
|
266
|
+
<code>ActiveAcl::OPTIONS</code> is an array that can be used to override various options for the system by setting
|
267
|
+
the values in environment.rb. <code>ActiveAcl::DEFAULT_OPTIONS</code> is as follows:
|
268
|
+
|
269
|
+
DEFAULT_OPTIONS = {
|
270
|
+
:acl_sections_table => 'acl_sections',
|
271
|
+
:acls_privileges_table => 'acls_privileges',
|
272
|
+
:acls_table => 'acls',
|
273
|
+
:privileges_table => 'privileges',
|
274
|
+
:requester_links_table => 'requester_links',
|
275
|
+
:target_links_table => 'target_links',
|
276
|
+
:requester_group_links_table => 'requester_group_links',
|
277
|
+
:target_group_links_table => 'target_group_links',
|
278
|
+
:controller_actions_table => 'controller_actions',
|
279
|
+
:controller_groups_table => 'controller_groups',
|
280
|
+
|
281
|
+
:controllers_group_name => 'unassigned_controller_actions', # the name of the base group
|
282
|
+
# that newly created controller groups get assigned to
|
283
|
+
:controller_group_name_suffix => '_controller', # name suffix for generated controller groups
|
284
|
+
|
285
|
+
:cache_permission_timeout => 10, # timeout in seconds for the second level cache
|
286
|
+
|
287
|
+
:db => ActiveAcl::DB::ActiveRecordAdapter, # the DB Adapter to use
|
288
|
+
:cache => ActiveAcl::Cache::NoCacheAdapter, # the Cache Adapter to use
|
289
|
+
}
|
290
|
+
|
291
|
+
==Tests
|
292
|
+
Not anymore, sorry. I'm using RSpec for testing. A repository
|
293
|
+
is/will be setup at http://github.com/popel/active_acl_plus_rspec/tree/master . Check it out and move it to "spec". Run
|
294
|
+
rake spec
|
295
|
+
|
296
|
+
==Credits
|
297
|
+
* Gregor Melhorn implemented this and maintained it up to Version 0.2.1. Thanks for releasing this!
|
298
|
+
* Evan for writing that great polymorph plugin and beeing so kind to add namespace and tablename support on Gregor's request.
|
299
|
+
* ReinH and markmeves for great support and suggestions at the rubyonrails channel on freenode.org.
|
300
|
+
* http://phpgacl.sourceforge.net as a great source of inspiration
|
301
|
+
* Obrie for writing plugin_migrations and loaded_plugins and also very nice support when Gregor got stuck with using them.
|
302
|
+
|
303
|
+
==ToDo/Ideas
|
304
|
+
|
305
|
+
in no particular order, just a reminder...
|
306
|
+
|
307
|
+
* add materialized_tree support
|
308
|
+
* use Moneta as key/value store
|
309
|
+
* direct PostgreSQL interface
|
310
|
+
* example on how to integrate with authentication
|
311
|
+
* example on controller actions
|
312
|
+
* error checking for conflicting ACL entries
|
313
|
+
* get all permissions for a requester within a section
|
314
|
+
* get all permissions of a requester (with one query)
|
315
|
+
* get all permissions of a requester on a target (with one query)
|
316
|
+
* get all requester with a given privilege on a target (one query)
|
317
|
+
* get all targets on which a requester has a certain privilege (one query)
|
data/VERSION.yml
ADDED
data/lib/active_acl.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveAcl
|
2
|
+
|
3
|
+
|
4
|
+
end
|
5
|
+
|
6
|
+
# plugin dependency
|
7
|
+
require 'has_many_polymorphs'
|
8
|
+
|
9
|
+
require 'active_acl/db/active_record_adapter'
|
10
|
+
require 'active_acl/cache/no_cache_adapter'
|
11
|
+
require 'active_acl/options'
|
12
|
+
require 'active_acl/base'
|
13
|
+
|
14
|
+
require 'active_acl/privilege_const_set'
|
15
|
+
require 'active_acl/grant'
|
16
|
+
|
17
|
+
require 'active_acl/handler/object_handler'
|
18
|
+
require 'active_acl/handler/nested_set'
|
19
|
+
require 'active_acl/load_controller_actions'
|
20
|
+
require 'active_acl/acts_as_access_object'
|
21
|
+
require 'active_acl/acts_as_access_group'
|
22
|
+
require 'active_acl/load_files_from'
|
23
|
+
|
24
|
+
|
25
|
+
# call class so its loaded and registered as access object
|
26
|
+
# wrap in rescue block so migrations don't fail
|
27
|
+
begin
|
28
|
+
ActiveAcl::ControllerAction
|
29
|
+
ActiveAcl::ControllerGroup
|
30
|
+
rescue StandardError => e
|
31
|
+
puts "Error #{e.message} #{e.backtrace.join("\n")}(need migrations?)"
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module ActiveAcl #:nodoc:
|
4
|
+
module Acts #:nodoc:
|
5
|
+
module AccessGroup #:nodoc:
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Extend self with access group capabilites.
|
13
|
+
# Options can be:
|
14
|
+
# type:: is mandatory and is one of the group handler classes
|
15
|
+
# left_column:: for ActiveAcl::Acts::AccessGroup::NestedSet grouped objects
|
16
|
+
# right_column:: for ActiveAcl::Acts::AccessGroup::NestedSet grouped objects
|
17
|
+
|
18
|
+
def acts_as_access_group(options = {})
|
19
|
+
type=options.delete(:type) || ActiveAcl::Acts::AccessGroup::NestedSet
|
20
|
+
ActiveAcl.register_group(self,type.new(options))
|
21
|
+
|
22
|
+
include ActiveAcl::Acts::Grant
|
23
|
+
include InstanceMethods
|
24
|
+
extend SingletonMethods
|
25
|
+
|
26
|
+
ActiveAcl::Acl.instance_eval do
|
27
|
+
has_many_polymorphs :requester_groups, {:from => ActiveAcl.from_classes,
|
28
|
+
:through => :"active_acl/requester_group_links",
|
29
|
+
:rename_individual_collections => true}
|
30
|
+
|
31
|
+
has_many_polymorphs :target_groups, {:from => ActiveAcl.from_classes,
|
32
|
+
:through => :"active_acl/target_group_links",
|
33
|
+
:rename_individual_collections => true}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module SingletonMethods
|
40
|
+
# class description in engine interface
|
41
|
+
def active_acl_description
|
42
|
+
name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
# override this to customize the description in the interface
|
48
|
+
def active_acl_description
|
49
|
+
to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveRecord::Base.send(:include, ActiveAcl::Acts::AccessGroup)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
#require 'direct_handler'
|
2
|
+
|
3
|
+
module ActiveAcl #:nodoc:
|
4
|
+
module Acts #:nodoc:
|
5
|
+
module AccessObject #:nodoc:
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Extend self with access object capabilites. See README for details
|
14
|
+
# on usage. Accepts the following options as a hash:
|
15
|
+
# grouped_by:: name of the association acting as a group for access privilege
|
16
|
+
# group_class_name:: class name of group class
|
17
|
+
# join_table:: name of the join table
|
18
|
+
# foreign_key:: foreign key of self in the join table
|
19
|
+
# association_foreign_key:: foreign_key of the group class
|
20
|
+
# habtm:: set to <code>true</code> if the grup is joined with a habtm association.
|
21
|
+
# If not specified, the plugin tries to guess if the association is
|
22
|
+
# has_and_belongs_to_many or belongs_to by creating the singular form of the
|
23
|
+
# :grouped_by option and comparing it to itself: If it matches, it assumes a belongs_to association.
|
24
|
+
def acts_as_access_object(options = {})
|
25
|
+
|
26
|
+
handler=ObjectHandler.new(self,options)
|
27
|
+
|
28
|
+
ActiveAcl.register_object(self,handler)
|
29
|
+
|
30
|
+
has_many :requester_links, :as => :requester, :dependent => :delete_all, :class_name => 'ActiveAcl::RequesterLink'
|
31
|
+
has_many :requester_acls, :through => :requester_links, :source => :acl, :class_name => 'ActiveAcl::Acl'
|
32
|
+
|
33
|
+
has_many :target_links, :as => :target, :dependent => :delete_all, :class_name => 'ActiveAcl::TargetLink'
|
34
|
+
has_many :target_acls, :through => :target_links, :source => :acl, :class_name => 'ActiveAcl::Acl'
|
35
|
+
|
36
|
+
include InstanceMethods
|
37
|
+
extend SingletonMethods
|
38
|
+
include ActiveAcl::Acts::Grant
|
39
|
+
|
40
|
+
ActiveAcl::Acl.instance_eval do
|
41
|
+
has_many_polymorphs :requesters, {:from => ActiveAcl.from_classes,
|
42
|
+
:through => :"active_acl/requester_links",
|
43
|
+
:rename_individual_collections => true}
|
44
|
+
|
45
|
+
has_many_polymorphs :targets, {:from => ActiveAcl.from_classes,
|
46
|
+
:through => :"active_acl/target_links",
|
47
|
+
:rename_individual_collections => true}
|
48
|
+
end
|
49
|
+
|
50
|
+
self.module_eval do
|
51
|
+
# checks if method is defined to not break tests
|
52
|
+
unless instance_methods.include? "reload_before_active_acl"
|
53
|
+
alias :reload_before_active_acl :reload
|
54
|
+
|
55
|
+
# Redefines reload, making shure privilege caches are cleared on reload
|
56
|
+
def reload #:nodoc:
|
57
|
+
active_acl_clear_cache!
|
58
|
+
reload_before_active_acl
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module SingletonMethods
|
67
|
+
# class description in engine interface
|
68
|
+
def active_acl_description
|
69
|
+
return name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module InstanceMethods
|
74
|
+
|
75
|
+
# checks if the user has a certain privilege, optionally on the given object.
|
76
|
+
# Option :on defines the target object.
|
77
|
+
def has_privilege?(privilege, options = {})
|
78
|
+
target = options[:on] #TODO: add error handling if not a hash
|
79
|
+
# no need to check anything if privilege is not a Privilege
|
80
|
+
raise "first Argument has to be a Privilege" unless privilege.is_a?(Privilege)
|
81
|
+
# no need to check anything if target is no Access Object
|
82
|
+
raise "target hast to be an AccessObject (#{target.class})" if target and !(target.class.respond_to?(:base_class) && ActiveAcl.is_access_object?(target.class))
|
83
|
+
|
84
|
+
active_acl_handler.has_privilege?(self,privilege,target)
|
85
|
+
end
|
86
|
+
def active_acl_handler
|
87
|
+
ActiveAcl.object_handler(self.class)
|
88
|
+
end
|
89
|
+
#returns a key value store
|
90
|
+
def active_acl_instance_cache
|
91
|
+
@active_acl_instance_cache ||= active_acl_handler.get_instance_cache(self)
|
92
|
+
end
|
93
|
+
#returns if the 2d acls are already cached
|
94
|
+
def active_acl_cached_2d?
|
95
|
+
!!active_acl_instance_cache[:prefetched_2d]
|
96
|
+
end
|
97
|
+
def active_acl_cached_2d!
|
98
|
+
active_acl_instance_cache[:prefetched_2d]=true
|
99
|
+
end
|
100
|
+
|
101
|
+
def active_acl_clear_cache!
|
102
|
+
@active_acl_instance_cache ={} #clear the lokal cache
|
103
|
+
active_acl_handler.delete_cached(self) #clear the 2 level cache
|
104
|
+
end
|
105
|
+
# override this to customize the description in the interface
|
106
|
+
def active_acl_description
|
107
|
+
to_s
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
ActiveRecord::Base.send(:include, ActiveAcl::Acts::AccessObject)
|