rend-acl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +26 -0
- data/README.md +25 -0
- data/Rakefile +9 -0
- data/ZEND_FRAMEWORK_LICENSE.txt +27 -0
- data/lib/rend/acl/exception.rb +6 -0
- data/lib/rend/acl/resource.rb +14 -0
- data/lib/rend/acl/role/registry/exception.rb +10 -0
- data/lib/rend/acl/role/registry.rb +168 -0
- data/lib/rend/acl/role.rb +19 -0
- data/lib/rend/acl/version.rb +7 -0
- data/lib/rend/acl.rb +875 -0
- data/rend-acl.gemspec +34 -0
- data/test/test_acl.rb +209 -0
- metadata +116 -0
data/lib/rend/acl.rb
ADDED
@@ -0,0 +1,875 @@
|
|
1
|
+
require 'rend/core'
|
2
|
+
require 'rend/acl/version'
|
3
|
+
require 'rend/acl/exception'
|
4
|
+
require 'rend/acl/role'
|
5
|
+
require 'rend/acl/resource'
|
6
|
+
|
7
|
+
module Rend
|
8
|
+
class Acl
|
9
|
+
include Rend::Core::Helpers::Php
|
10
|
+
|
11
|
+
TYPE_ALLOW = :TYPE_ALLOW
|
12
|
+
TYPE_DENY = :TYPE_DENY
|
13
|
+
OP_ADD = :OP_ADD
|
14
|
+
OP_REMOVE = :OP_REMOVE
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
# @var Rend::Acl::Role::Registry
|
18
|
+
@_role_registry = nil
|
19
|
+
|
20
|
+
# @var Hash
|
21
|
+
@_resources = {}
|
22
|
+
|
23
|
+
# @var Rend::Acl::Role
|
24
|
+
@_is_allowed_role = nil
|
25
|
+
|
26
|
+
# @var Rend::Acl::Resource
|
27
|
+
@_is_allowed_resource = nil
|
28
|
+
|
29
|
+
# @var String
|
30
|
+
@_is_allowed_privilege = nil
|
31
|
+
|
32
|
+
# ACL rules whitelist (deny everything to all) by default
|
33
|
+
# @var Hash
|
34
|
+
@_rules = {
|
35
|
+
:all_resources => {
|
36
|
+
:all_roles => {
|
37
|
+
:all_privileges => {
|
38
|
+
:type => TYPE_DENY
|
39
|
+
},
|
40
|
+
:by_privilege_id => {}
|
41
|
+
},
|
42
|
+
:by_role_id => {}
|
43
|
+
},
|
44
|
+
:by_resource_id => {}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds a Role having an identifier unique to the registry
|
49
|
+
#
|
50
|
+
# The parents parameter may be a reference to, or the string identifier for,
|
51
|
+
# a Role existing in the registry, or parents may be passed as an array of
|
52
|
+
# these - mixing string identifiers and objects is ok - to indicate the Roles
|
53
|
+
# from which the newly added Role will directly inherit.
|
54
|
+
#
|
55
|
+
# In order to resolve potential ambiguities with conflicting rules inherited
|
56
|
+
# from different parents, the most recently added parent takes precedence over
|
57
|
+
# parents that were previously added. In other words, the first parent added
|
58
|
+
# will have the least priority, and the last parent added will have the
|
59
|
+
# highest priority.
|
60
|
+
#
|
61
|
+
# @param Rend::Acl::Role|string role
|
62
|
+
# @param Rend::Acl::Role|string|array parents
|
63
|
+
# @uses Rend::Acl::Role::Registry::add?()
|
64
|
+
# @return Rend::Acl Provides a fluent interface
|
65
|
+
def add_role!(role, parents = nil)
|
66
|
+
role = Rend::Acl::Role.new(role) if role.is_a?(String)
|
67
|
+
type_hint! Rend::Acl::Role, role, :is_required => true
|
68
|
+
role_registry.add!(role, parents)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the identified Role
|
73
|
+
#
|
74
|
+
# The role parameter can either be a Role or Role identifier.
|
75
|
+
#
|
76
|
+
# @param Rend::Acl::Role|string role
|
77
|
+
# @uses Rend::Acl::Role::Registry::get!()
|
78
|
+
# @return Rend::Acl::Role
|
79
|
+
def role(role)
|
80
|
+
role_registry.get!(role)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns true if and only if the Role exists in the registry
|
84
|
+
#
|
85
|
+
# The role parameter can either be a Role or a Role identifier.
|
86
|
+
#
|
87
|
+
# @param Rend::Acl::Role|string role
|
88
|
+
# @uses Rend::Acl::Role::Registry::has?()
|
89
|
+
# @return boolean
|
90
|
+
def has_role?(role)
|
91
|
+
role_registry.has?(role)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns true if and only if role inherits from inherit
|
95
|
+
#
|
96
|
+
# Both parameters may be either a Role or a Role identifier. If
|
97
|
+
# only_parents is true, then role must inherit directly from
|
98
|
+
# inherit in order to return true. By default, this method looks
|
99
|
+
# through the entire inheritance DAG to determine whether role
|
100
|
+
# inherits from inherit through its ancestor Roles.
|
101
|
+
#
|
102
|
+
# @param Rend::Acl::Role|string role
|
103
|
+
# @param Rend::Acl::Role|string inherit
|
104
|
+
# @param boolean only_parents
|
105
|
+
# @uses Rend::Acl::Role::Registry::inherits?()
|
106
|
+
# @return boolean
|
107
|
+
def inherits_role?(role, inherit, only_parents = false)
|
108
|
+
role_registry.inherits?(role, inherit, only_parents)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Removes the Role from the registry
|
112
|
+
#
|
113
|
+
# The role parameter can either be a Role or a Role identifier.
|
114
|
+
#
|
115
|
+
# @param Rend::Acl::Role|string role
|
116
|
+
# @uses Rend::Acl::Role::Registry::remove()
|
117
|
+
# @return Rend::Acl Provides a fluent interface
|
118
|
+
def remove_role!(role)
|
119
|
+
role_registry.remove!(role)
|
120
|
+
|
121
|
+
role_id = (role.class <= Rend::Acl::Role) ? role.id : role
|
122
|
+
|
123
|
+
@_rules[:all_resources][:by_role_id].each do |role_id_current, rules|
|
124
|
+
if role_id == role_id_current
|
125
|
+
@_rules[:all_resources][:by_role_id].delete(role_id_current)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@_rules[:by_resource_id].each do |resource_id_current, visitor|
|
130
|
+
if visitor.has_key?(:by_role_id)
|
131
|
+
visitor[:by_role_id].each do |role_id_current, rules|
|
132
|
+
if role_id == role_id_current
|
133
|
+
@_rules[:by_resource_id][resource_id_current][:by_role_id].delete(role_id_current)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# Removes all Roles from the registry
|
143
|
+
#
|
144
|
+
# @uses Rend::Acl::Role::Registry::remove_all!()
|
145
|
+
# @return Rend::Acl Provides a fluent interface
|
146
|
+
def remove_role_all!
|
147
|
+
role_registry.remove_all!
|
148
|
+
|
149
|
+
@_rules[:all_resources][:by_role_id].each do |role_id_current, rules|
|
150
|
+
@_rules[:all_resources][:by_role_id].delete(role_id_current)
|
151
|
+
end
|
152
|
+
|
153
|
+
@_rules[:by_resource_id].each do |resource_id_current, visitor|
|
154
|
+
visitor[:by_role_id].each do |role_id_current, rules|
|
155
|
+
@_rules[:by_resource_id][resource_id_current][:by_role_id].delete(role_id_current)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
# Adds a Resource having an identifier unique to the ACL
|
163
|
+
#
|
164
|
+
# The parent parameter may be a reference to, or the string identifier for,
|
165
|
+
# the existing Resource from which the newly added Resource will inherit.
|
166
|
+
#
|
167
|
+
# @param Rend::Acl::Resource|string resource
|
168
|
+
# @param Rend::Acl::Resource|string parent
|
169
|
+
# @throws Rend::Acl::Exception
|
170
|
+
# @return Rend::Acl Provides a fluent interface
|
171
|
+
def add_resource!(resource, parent = nil)
|
172
|
+
resource = Rend::Acl::Resource.new(resource) if resource.is_a?(String)
|
173
|
+
type_hint! Rend::Acl::Resource, resource, :is_required => true
|
174
|
+
|
175
|
+
resource_id = resource.id
|
176
|
+
|
177
|
+
raise Rend::Acl::Exception, "Resource id 'resource_id' already exists in the ACL" if has?(resource_id)
|
178
|
+
|
179
|
+
resource_parent = nil
|
180
|
+
|
181
|
+
if parent
|
182
|
+
begin
|
183
|
+
resource_parent_id = (parent.class <= Rend::Acl::Resource) ? parent.id : parent
|
184
|
+
resource_parent = get!(resource_parent_id)
|
185
|
+
rescue Rend::Acl::Exception
|
186
|
+
raise Rend::Acl::Exception, "Parent Resource id 'resource_parent_id' does not exist"
|
187
|
+
end
|
188
|
+
@_resources[resource_parent_id][:children][resource_id] = resource
|
189
|
+
end
|
190
|
+
|
191
|
+
@_resources[resource_id] = { :instance => resource, :parent => resource_parent, :children => {} }
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns the identified Resource
|
196
|
+
#
|
197
|
+
# The resource parameter can either be a Resource or a Resource identifier.
|
198
|
+
#
|
199
|
+
# @param Rend::Acl::Resource|string resource
|
200
|
+
# @throws Rend::Acl::Exception
|
201
|
+
# @return Rend::Acl::Resource
|
202
|
+
|
203
|
+
def get!(resource)
|
204
|
+
resource_id = (resource.class <= Rend::Acl::Resource) ? resource.id : resource.to_s
|
205
|
+
raise Rend::Acl::Exception, "Resource 'resource_id' not found" unless has?(resource)
|
206
|
+
@_resources[resource_id][:instance]
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns true if and only if the Resource exists in the ACL
|
210
|
+
#
|
211
|
+
# The resource parameter can either be a Resource or a Resource identifier.
|
212
|
+
#
|
213
|
+
# @param Rend::Acl::Resource|string resource
|
214
|
+
# @return boolean
|
215
|
+
def has?(resource)
|
216
|
+
resource_id = (resource.class <= Rend::Acl::Resource) ? resource.id : resource.to_s
|
217
|
+
@_resources.keys.include?(resource_id)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns true if and only if resource inherits from inherit
|
221
|
+
#
|
222
|
+
# Both parameters may be either a Resource or a Resource identifier. If
|
223
|
+
# only_parent is true, then resource must inherit directly from
|
224
|
+
# inherit in order to return true. By default, this method looks
|
225
|
+
# through the entire inheritance tree to determine whether resource
|
226
|
+
# inherits from inherit through its ancestor Resources.
|
227
|
+
#
|
228
|
+
# @param Rend::Acl::Resource|string resource
|
229
|
+
# @param Rend::Acl::Resource|string inherit
|
230
|
+
# @param boolean onlyParent
|
231
|
+
# @throws Rend_Acl_Resource_Registry_Exception
|
232
|
+
# @return boolean
|
233
|
+
def inherits?(resource, inherit, only_parent = false)
|
234
|
+
resource_id = get!(resource).id
|
235
|
+
inherit_id = get!(inherit).id
|
236
|
+
|
237
|
+
if @_resources[resource_id][:parent]
|
238
|
+
parent_id = @_resources[resource_id][:parent].id
|
239
|
+
return true if inherit_id == parent_id
|
240
|
+
return false if only_parent
|
241
|
+
else
|
242
|
+
return false
|
243
|
+
end
|
244
|
+
|
245
|
+
while @_resources[parent_id][:parent]
|
246
|
+
parent_id = @_resources[parent_id][:parent].id
|
247
|
+
return true if inherit_id == parent_id
|
248
|
+
end
|
249
|
+
false
|
250
|
+
end
|
251
|
+
|
252
|
+
# Removes a Resource and all of its children
|
253
|
+
#
|
254
|
+
# The resource parameter can either be a Resource or a Resource identifier.
|
255
|
+
#
|
256
|
+
# @param Rend::Acl::Resource|string resource
|
257
|
+
# @throws Rend::Acl::Exception
|
258
|
+
# @return Rend::Acl Provides a fluent interface
|
259
|
+
def remove!(resource)
|
260
|
+
resource_id = get!(resource).id
|
261
|
+
resources_removed = [resource_id]
|
262
|
+
|
263
|
+
if resource_parent = @_resources[resource_id][:parent]
|
264
|
+
@_resources[resource_parent.id][:children].delete(resource_id)
|
265
|
+
end
|
266
|
+
|
267
|
+
@_resources[resource_id][:children].each do |child_id, child|
|
268
|
+
remove!(child_id)
|
269
|
+
resources_removed.push(child_id)
|
270
|
+
end
|
271
|
+
|
272
|
+
resources_removed.each do |resource_id_removed|
|
273
|
+
@_rules[:by_resource_id].each do |resource_id_current, rules|
|
274
|
+
if resource_id_removed == resource_id_current
|
275
|
+
@_rules[:by_resource_id].delete(resource_id_current)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
@_resources.delete(resource_id)
|
281
|
+
|
282
|
+
self
|
283
|
+
end
|
284
|
+
|
285
|
+
# Removes all Resources
|
286
|
+
#
|
287
|
+
# @return Rend::Acl Provides a fluent interface
|
288
|
+
def remove_all!
|
289
|
+
@_resources.each do |resource_id, resource|
|
290
|
+
@_rules[:by_resource_id].each do |resource_id_current, rules|
|
291
|
+
@_rules[:by_resource_id].delete(resource_id_current) if resource_id == resource_id_current
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
@_resources = {}
|
296
|
+
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
# Adds an "allow" rule to the ACL
|
301
|
+
#
|
302
|
+
# @param Rend::Acl::Role|string|array roles
|
303
|
+
# @param Rend::Acl::Resource|string|array resources
|
304
|
+
# @param string|array privileges
|
305
|
+
# @uses Rend::Acl::set_rule!()
|
306
|
+
# @return Rend::Acl Provides a fluent interface
|
307
|
+
def allow!(roles = nil, resources = nil, privileges = nil)
|
308
|
+
set_rule!(OP_ADD, TYPE_ALLOW, roles, resources, privileges)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Adds a "deny" rule to the ACL
|
312
|
+
#
|
313
|
+
# @param Rend::Acl::Role|string|array roles
|
314
|
+
# @param Rend::Acl::Resource|string|array resources
|
315
|
+
# @param string|array privileges
|
316
|
+
# @uses Rend::Acl::set_rule!()
|
317
|
+
# @return Rend::Acl Provides a fluent interface
|
318
|
+
def deny!(roles = nil, resources = nil, privileges = nil)
|
319
|
+
set_rule!(OP_ADD, TYPE_DENY, roles, resources, privileges)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Removes "allow" permissions from the ACL
|
323
|
+
#
|
324
|
+
# @param Rend::Acl::Role|string|array roles
|
325
|
+
# @param Rend::Acl::Resource|string|array resources
|
326
|
+
# @param string|array privileges
|
327
|
+
# @uses Rend::Acl::set_rule!()
|
328
|
+
# @return Rend::Acl Provides a fluent interface
|
329
|
+
def remove_allow!(roles = nil, resources = nil, privileges = nil)
|
330
|
+
set_rule!(OP_REMOVE, TYPE_ALLOW, roles, resources, privileges)
|
331
|
+
end
|
332
|
+
|
333
|
+
# Removes "deny" restrictions from the ACL
|
334
|
+
#
|
335
|
+
# @param Rend::Acl::Role|string|array roles
|
336
|
+
# @param Rend::Acl::Resource|string|array resources
|
337
|
+
# @param string|array privileges
|
338
|
+
# @uses Rend::Acl::set_rule!()
|
339
|
+
# @return Rend::Acl Provides a fluent interface
|
340
|
+
def remove_deny!(roles = nil, resources = nil, privileges = nil)
|
341
|
+
set_rule!(OP_REMOVE, TYPE_DENY, roles, resources, privileges)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Performs operations on ACL rules
|
345
|
+
#
|
346
|
+
# The operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the
|
347
|
+
# user wants to add or remove a rule, respectively:
|
348
|
+
#
|
349
|
+
# OP_ADD specifics:
|
350
|
+
#
|
351
|
+
# A rule is added that would allow one or more Roles access to [certain privileges
|
352
|
+
# upon] the specified Resource(s).
|
353
|
+
#
|
354
|
+
# OP_REMOVE specifics:
|
355
|
+
#
|
356
|
+
# The rule is removed only in the context of the given Roles, Resources, and privileges.
|
357
|
+
# Existing rules to which the remove operation does not apply would remain in the
|
358
|
+
# ACL.
|
359
|
+
#
|
360
|
+
# The type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the
|
361
|
+
# rule is intended to allow or deny permission, respectively.
|
362
|
+
#
|
363
|
+
# The roles and resources parameters may be references to, or the string identifiers for,
|
364
|
+
# existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers
|
365
|
+
# and objects is ok - to indicate the Resources and Roles to which the rule applies. If either
|
366
|
+
# roles or resources is nil, then the rule applies to all Roles or all Resources, respectively.
|
367
|
+
# Both may be nil in order to work with the default rule of the ACL.
|
368
|
+
#
|
369
|
+
# The privileges parameter may be used to further specify that the rule applies only
|
370
|
+
# to certain privileges upon the Resource(s) in question. This may be specified to be a single
|
371
|
+
# privilege with a string, and multiple privileges may be specified as an array of strings.
|
372
|
+
#
|
373
|
+
#
|
374
|
+
# @param string operation
|
375
|
+
# @param string type
|
376
|
+
# @param Rend::Acl::Role|string|array roles
|
377
|
+
# @param Rend::Acl::Resource|string|array resources
|
378
|
+
# @param string|array privileges
|
379
|
+
# @throws Rend::Acl::Exception
|
380
|
+
# @uses Rend::Acl::Role::Registry::get!()
|
381
|
+
# @uses Rend::Acl::get!()
|
382
|
+
# @return Rend::Acl Provides a fluent interface
|
383
|
+
def set_rule!(operation, type, roles = nil, resources = nil, privileges = nil)
|
384
|
+
# ensure that the rule type is valid normalize input to uppercase
|
385
|
+
type = type.upcase
|
386
|
+
if type != TYPE_ALLOW && type != TYPE_DENY
|
387
|
+
raise Zend::Acl::Exception, "Unsupported rule type must be either '#{TYPE_ALLOW}' or '#{TYPE_DENY}'"
|
388
|
+
end
|
389
|
+
|
390
|
+
# ensure that all specified Roles exist normalize input to array of Role objects or nil
|
391
|
+
if !roles.is_a?(Array)
|
392
|
+
roles = [roles]
|
393
|
+
elsif roles.empty?
|
394
|
+
roles = [nil]
|
395
|
+
end
|
396
|
+
roles = roles.reduce([]) do |seed, role|
|
397
|
+
seed << (role ? role_registry.get!(role) : nil)
|
398
|
+
end
|
399
|
+
|
400
|
+
# ensure that all specified Resources exist normalize input to array of Resource objects or nil
|
401
|
+
if resources
|
402
|
+
resources = Array(resources)
|
403
|
+
resources << nil if resources.empty?
|
404
|
+
resources = resources.reduce([]) do |seed, resource|
|
405
|
+
seed << (resource ? get!(resource) : nil)
|
406
|
+
end
|
407
|
+
else
|
408
|
+
# this might be used later if resource iteration is required
|
409
|
+
all_resources = @_resources.reduce([]) do |seed, resource|
|
410
|
+
seed << resource[:instance]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# normalize privileges to array
|
415
|
+
if privileges.nil?
|
416
|
+
privileges = []
|
417
|
+
elsif !privileges.is_a?(Array)
|
418
|
+
privileges = [privileges]
|
419
|
+
end
|
420
|
+
|
421
|
+
case operation
|
422
|
+
|
423
|
+
# add to the rules
|
424
|
+
when OP_ADD
|
425
|
+
if resources
|
426
|
+
# this block will iterate the provided resources
|
427
|
+
resources.each do |resource|
|
428
|
+
roles.each do |role|
|
429
|
+
rules = _rules(resource, role, true)
|
430
|
+
if privileges.empty?
|
431
|
+
rules[:all_privileges][:type] = type
|
432
|
+
rules[:by_privilege_id] = {} unless rules.has_key?(:by_privilege_id)
|
433
|
+
else
|
434
|
+
privileges.each do |privilege|
|
435
|
+
rules[:by_privilege_id][privilege] = {:type => type}
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
else
|
441
|
+
# this block will apply to all resources in a global rule
|
442
|
+
roles.each do |role|
|
443
|
+
rules = _rules(nil, role, true)
|
444
|
+
if privileges.empty?
|
445
|
+
rules[:all_privileges] = {:type => type}
|
446
|
+
else
|
447
|
+
privileges.each do |privilege|
|
448
|
+
rules[:by_privilege_id][privilege] = {:type => type}
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
# remove from the rules
|
454
|
+
when OP_REMOVE
|
455
|
+
if resources
|
456
|
+
# this block will iterate the provided resources
|
457
|
+
resources.each do |resource|
|
458
|
+
roles.each do |role|
|
459
|
+
rules = _rules(resource, role)
|
460
|
+
next if rules.nil?
|
461
|
+
if privileges.empty?
|
462
|
+
if resource.nil? && role.nil?
|
463
|
+
if rules[:all_privileges][:type] == type
|
464
|
+
rules.replace({
|
465
|
+
:all_privileges => { :type => TYPE_DENY },
|
466
|
+
:by_privilege_id => {}
|
467
|
+
})
|
468
|
+
end
|
469
|
+
next
|
470
|
+
end
|
471
|
+
|
472
|
+
if rules[:all_privileges].has_key?(:type) && rules[:all_privileges][:type] == type
|
473
|
+
rules.delete(:all_privileges)
|
474
|
+
end
|
475
|
+
else
|
476
|
+
privileges.each do |privilege|
|
477
|
+
if rules[:by_privilege_id].has_key?(privilege) && rules[:by_privilege_id][privilege][:type] == type
|
478
|
+
rules[:by_privilege_id].delete(privilege)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
else
|
485
|
+
# this block will apply to all resources in a global rule
|
486
|
+
roles.each do |role|
|
487
|
+
|
488
|
+
# since nil (all resources) was passed to this set_role!() call, we need
|
489
|
+
# clean up all the rules for the global allResources, as well as the indivually
|
490
|
+
# set resources (per privilege as well)
|
491
|
+
[nil].concat(all_resources).each do |resource|
|
492
|
+
rules = _rules(resource, role, true)
|
493
|
+
next if rules.nil?
|
494
|
+
if privileges.empty?
|
495
|
+
if role.nil?
|
496
|
+
if rules[:all_privileges][:type] == type
|
497
|
+
rules.replace({
|
498
|
+
:all_privileges => { :type => TYPE_DENY },
|
499
|
+
:by_privilege_id => {}
|
500
|
+
})
|
501
|
+
end
|
502
|
+
next
|
503
|
+
end
|
504
|
+
|
505
|
+
if rules[:all_privileges].has_key?(:type) && rules[:all_privileges][:type] == type
|
506
|
+
rules.delete(:all_privileges)
|
507
|
+
end
|
508
|
+
else
|
509
|
+
privileges.each do |privilege|
|
510
|
+
if rules[:by_privilege_id].has_key?(privilege) && rules[:by_privilege_id][privilege][:type] == type
|
511
|
+
rules[:by_privilege_id].delete(privilege)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
else
|
519
|
+
raise Rend::Acl::Exception, "Unsupported operation must be either '#{OP_ADD}' or '#{OP_REMOVE}'"
|
520
|
+
end
|
521
|
+
|
522
|
+
self
|
523
|
+
end
|
524
|
+
|
525
|
+
# Returns true if and only if the Role has access to the Resource
|
526
|
+
#
|
527
|
+
# The role and resource parameters may be references to, or the string identifiers for,
|
528
|
+
# an existing Resource and Role combination.
|
529
|
+
#
|
530
|
+
# If either role or resource is nil, then the query applies to all Roles or all Resources,
|
531
|
+
# respectively. Both may be nil to query whether the ACL has a "blacklist" rule
|
532
|
+
# (allow everything to all). By default, Rend::Acl creates a "whitelist" rule (deny
|
533
|
+
# everything to all), and this method would return false unless this default has
|
534
|
+
# been overridden (i.e., by executing acl->allow()).
|
535
|
+
#
|
536
|
+
# If a privilege is not provided, then this method returns false if and only if the
|
537
|
+
# Role is denied access to at least one privilege upon the Resource. In other words, this
|
538
|
+
# method returns true if and only if the Role is allowed all privileges on the Resource.
|
539
|
+
#
|
540
|
+
# This method checks Role inheritance using a depth-first traversal of the Role registry.
|
541
|
+
# The highest priority parent (i.e., the parent most recently added) is checked first,
|
542
|
+
# and its respective parents are checked similarly before the lower-priority parents of
|
543
|
+
# the Role are checked.
|
544
|
+
#
|
545
|
+
# @param Rend::Acl::Role|string role
|
546
|
+
# @param Rend::Acl::Resource|string resource
|
547
|
+
# @param string privilege
|
548
|
+
# @uses Rend::Acl::get!()
|
549
|
+
# @uses Rend::Acl::Role::Registry::get!()
|
550
|
+
# @return boolean
|
551
|
+
def allowed?(role = nil, resource = nil, privilege = nil)
|
552
|
+
# reset role & resource to nil
|
553
|
+
@_is_allowed_role = nil
|
554
|
+
@_is_allowed_resource = nil
|
555
|
+
@_is_allowed_privilege = nil
|
556
|
+
|
557
|
+
if role
|
558
|
+
# keep track of originally called role
|
559
|
+
@_is_allowed_role = role
|
560
|
+
role = role_registry.get!(role)
|
561
|
+
@_is_allowed_role = role unless @_is_allowed_role.class <= Rend::Acl::Role
|
562
|
+
end
|
563
|
+
|
564
|
+
if resource
|
565
|
+
# keep track of originally called resource
|
566
|
+
@_is_allowed_resource = resource
|
567
|
+
resource = get!(resource)
|
568
|
+
unless @_is_allowed_resource.class <= Rend::Acl::Resource
|
569
|
+
@_is_allowed_resource = resource
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
if privilege.nil?
|
574
|
+
# query on all privileges
|
575
|
+
loop do # loop terminates at :all_resources pseudo-parent
|
576
|
+
# depth-first search on role if it is not :all_roles pseudo-parent
|
577
|
+
result = _role_dfs_all_privileges(role, resource)
|
578
|
+
return result if role && result
|
579
|
+
|
580
|
+
# look for rule on :all_roles psuedo-parent
|
581
|
+
rules = _rules(resource, nil)
|
582
|
+
if rules
|
583
|
+
rules[:by_privilege_id].each do |priv, rule|
|
584
|
+
rule_type_one_privilege = _rule_type(resource, nil, priv)
|
585
|
+
return false if rule_type_one_privilege == TYPE_DENY
|
586
|
+
end
|
587
|
+
rule_type_one_privilege = _rule_type(resource, nil, nil)
|
588
|
+
return rule_type_one_privilege == TYPE_ALLOW if rule_type_one_privilege
|
589
|
+
end
|
590
|
+
|
591
|
+
# try next Resource
|
592
|
+
resource = @_resources[resource.id][:parent]
|
593
|
+
end
|
594
|
+
else
|
595
|
+
# IN HERE
|
596
|
+
@_is_allowed_privilege = privilege
|
597
|
+
# query on one privilege
|
598
|
+
loop do # loop terminates at :all_resources pseudo-parent
|
599
|
+
# depth-first search on role if it is not :all_roles pseudo-parent
|
600
|
+
if nil != role && nil != (result = _role_dfs_one_privilege(role, resource, privilege))
|
601
|
+
return result
|
602
|
+
end
|
603
|
+
|
604
|
+
|
605
|
+
# look for rule on 'allRoles' pseudo-parent
|
606
|
+
if nil != (rule_type = _rule_type(resource, nil, privilege))
|
607
|
+
return TYPE_ALLOW == rule_type
|
608
|
+
elsif nil != (rule_type_all_privileges = _rule_type(resource, nil, nil))
|
609
|
+
return TYPE_ALLOW == rule_type_all_privileges
|
610
|
+
end
|
611
|
+
|
612
|
+
# try next Resource
|
613
|
+
resource = @_resources[resource.id][:parent]
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
# Returns the Role registry for this ACL
|
619
|
+
#
|
620
|
+
# If no Role registry has been created yet, a new default Role registry
|
621
|
+
# is created and returned.
|
622
|
+
#
|
623
|
+
# @return Rend::Acl::Role::Registry
|
624
|
+
def role_registry
|
625
|
+
@_role_registry ||= Rend::Acl::Role::Registry.new
|
626
|
+
end
|
627
|
+
|
628
|
+
# Performs a depth-first search of the Role DAG, starting at role, in order to find a rule
|
629
|
+
# allowing/denying role access to all privileges upon resource
|
630
|
+
#
|
631
|
+
# This method returns true if a rule is found and allows access. If a rule exists and denies access,
|
632
|
+
# then this method returns false. If no applicable rule is found, then this method returns nil.
|
633
|
+
#
|
634
|
+
# @param Rend::Acl::Role role
|
635
|
+
# @param Rend::Acl::Resource resource
|
636
|
+
# @return boolean|nil
|
637
|
+
def _role_dfs_all_privileges(role, resource = nil)
|
638
|
+
type_hint! Rend::Acl::Role, role, :is_required => true
|
639
|
+
type_hint! Rend::Acl::Resource, resource
|
640
|
+
|
641
|
+
dfs = {:visited => {}, :stack => []}
|
642
|
+
|
643
|
+
result = _role_dfs_visit_all_privileges(role, resource, dfs)
|
644
|
+
return result unless result.nil?
|
645
|
+
|
646
|
+
while role = dfs[:stack].pop
|
647
|
+
unless dfs[:visited].has_key?(role.id)
|
648
|
+
result = _role_dfs_visit_all_privileges(role, resource, dfs)
|
649
|
+
return result unless result.nil?
|
650
|
+
end
|
651
|
+
end
|
652
|
+
nil
|
653
|
+
end
|
654
|
+
|
655
|
+
# Visits an role in order to look for a rule allowing/denying role access to all privileges upon resource
|
656
|
+
#
|
657
|
+
# This method returns true if a rule is found and allows access. If a rule exists and denies access,
|
658
|
+
# then this method returns false. If no applicable rule is found, then this method returns nil.
|
659
|
+
#
|
660
|
+
# This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
|
661
|
+
#
|
662
|
+
# @param Rend::Acl::Role role
|
663
|
+
# @param Rend::Acl::Resource resource
|
664
|
+
# @param array dfs
|
665
|
+
# @return boolean|nil
|
666
|
+
# @throws Rend::Acl::Exception
|
667
|
+
def _role_dfs_visit_all_privileges(role, resource = nil, dfs = nil)
|
668
|
+
type_hint! Rend::Acl::Role, role, :is_required => true
|
669
|
+
type_hint! Rend::Acl::Resource, resource
|
670
|
+
raise Rend::Acl::Exception, 'dfs parameter may not be nil' if dfs.nil?
|
671
|
+
|
672
|
+
if rules = _rules(resource, role)
|
673
|
+
rules[:by_privilege_id].each do |privilege, rule|
|
674
|
+
rule_type_one_privilege = _rule_type(resource, role, privilege)
|
675
|
+
return false if rule_type_one_privilege == TYPE_DENY
|
676
|
+
end
|
677
|
+
rule_type_all_privileges = _rule_type(resource, role, nil)
|
678
|
+
return rule_type_all_privileges == TYPE_ALLOW unless rule_type_all_privileges.nil?
|
679
|
+
end
|
680
|
+
|
681
|
+
dfs[:visited][role.id] = true
|
682
|
+
role_registry.parents(role).each do |role_parent_id, role_parent|
|
683
|
+
dfs[:stack].push(role_parent)
|
684
|
+
end
|
685
|
+
nil
|
686
|
+
end
|
687
|
+
|
688
|
+
# Performs a depth-first search of the Role DAG, starting at role, in order to find a rule
|
689
|
+
# allowing/denying role access to a privilege upon resource
|
690
|
+
#
|
691
|
+
# This method returns true if a rule is found and allows access. If a rule exists and denies access,
|
692
|
+
# then this method returns false. If no applicable rule is found, then this method returns nil.
|
693
|
+
#
|
694
|
+
# @param Rend::Acl::Role role
|
695
|
+
# @param Rend::Acl::Resource resource
|
696
|
+
# @param string privilege
|
697
|
+
# @return boolean|nil
|
698
|
+
# @throws Rend::Acl::Exception
|
699
|
+
def _role_dfs_one_privilege(role, resource = nil, privilege = nil)
|
700
|
+
type_hint! Rend::Acl::Role, role, :is_required => true
|
701
|
+
type_hint! Rend::Acl::Resource, resource
|
702
|
+
raise Rend::Acl::Exception, 'privilege parameter may not be nil' if privilege.nil?
|
703
|
+
|
704
|
+
dfs = {:visited => {}, :stack => []}
|
705
|
+
|
706
|
+
result = _role_dfs_visit_one_privilege(role, resource, privilege, dfs)
|
707
|
+
return result unless result.nil?
|
708
|
+
|
709
|
+
while role = dfs[:stack].pop
|
710
|
+
unless dfs[:visited].has_key?(role.id)
|
711
|
+
result = _role_dfs_visit_one_privilege(role, resource, privilege, dfs)
|
712
|
+
return result unless result.nil?
|
713
|
+
end
|
714
|
+
end
|
715
|
+
nil
|
716
|
+
end
|
717
|
+
|
718
|
+
# Visits an role in order to look for a rule allowing/denying role access to a privilege upon resource
|
719
|
+
#
|
720
|
+
# This method returns true if a rule is found and allows access. If a rule exists and denies access,
|
721
|
+
# then this method returns false. If no applicable rule is found, then this method returns nil.
|
722
|
+
#
|
723
|
+
# This method is used by the internal depth-first search algorithm and may modify the DFS data structure.
|
724
|
+
#
|
725
|
+
# @param Rend::Acl::Role role
|
726
|
+
# @param Rend::Acl::Resource resource
|
727
|
+
# @param string privilege
|
728
|
+
# @param array dfs
|
729
|
+
# @return boolean|nil
|
730
|
+
# @throws Rend::Acl::Exception
|
731
|
+
def _role_dfs_visit_one_privilege(role, resource = nil, privilege = nil, dfs = nil)
|
732
|
+
type_hint! Rend::Acl::Role, role, :is_required => true
|
733
|
+
type_hint! Rend::Acl::Resource, resource
|
734
|
+
raise Rend::Acl::Exception, 'privilege parameter may not be nil' if privilege.nil?
|
735
|
+
raise Rend::Acl::Exception, 'dfs parameter may not be nil' if dfs.nil?
|
736
|
+
|
737
|
+
|
738
|
+
if rule_type_one_privilege = _rule_type(resource, role, privilege)
|
739
|
+
return rule_type_one_privilege == TYPE_ALLOW
|
740
|
+
end
|
741
|
+
|
742
|
+
if rule_type_all_privileges = _rule_type(resource, role, nil)
|
743
|
+
return rule_type_all_privileges == TYPE_ALLOW
|
744
|
+
end
|
745
|
+
|
746
|
+
dfs[:visited][role.id] = true
|
747
|
+
role_registry.parents(role).each do |role_parent_id, role_parent|
|
748
|
+
dfs[:stack].push(role_parent)
|
749
|
+
end
|
750
|
+
nil
|
751
|
+
end
|
752
|
+
|
753
|
+
# Returns the rule type associated with the specified Resource, Role, and privilege
|
754
|
+
# combination.
|
755
|
+
#
|
756
|
+
# If a rule does not exist then this method returns nil. Otherwise, the
|
757
|
+
# rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY.
|
758
|
+
#
|
759
|
+
# If resource or role is nil, then this means that the rule must apply to
|
760
|
+
# all Resources or Roles, respectively.
|
761
|
+
#
|
762
|
+
# If privilege is nil, then the rule must apply to all privileges.
|
763
|
+
#
|
764
|
+
# If all three parameters are nil, then the default ACL rule type is returned,
|
765
|
+
# based on whether its assertion method passes.
|
766
|
+
#
|
767
|
+
# @param Rend::Acl::Resource resource
|
768
|
+
# @param Rend::Acl::Role role
|
769
|
+
# @param string privilege
|
770
|
+
# @return string|nil
|
771
|
+
def _rule_type(resource = nil, role = nil, privilege = nil)
|
772
|
+
type_hint! Rend::Acl::Resource, resource
|
773
|
+
type_hint! Rend::Acl::Role, role
|
774
|
+
|
775
|
+
# get the rules for the resource and role
|
776
|
+
return nil unless rules = _rules(resource, role)
|
777
|
+
|
778
|
+
# follow privilege
|
779
|
+
if privilege.nil?
|
780
|
+
if rules.has_key?(:all_privileges)
|
781
|
+
rule = rules[:all_privileges]
|
782
|
+
else
|
783
|
+
return nil
|
784
|
+
end
|
785
|
+
elsif !rules || !rules[:by_privilege_id].has_key?(privilege)
|
786
|
+
return nil
|
787
|
+
else
|
788
|
+
rule = rules[:by_privilege_id][privilege]
|
789
|
+
end
|
790
|
+
|
791
|
+
# check assertion first
|
792
|
+
assertion_value = nil
|
793
|
+
if rule[:assert]
|
794
|
+
# assertion = rule[:assert]
|
795
|
+
# assertion_value = assertion.assert(
|
796
|
+
# self,
|
797
|
+
# (@_isAllowedRole instanceof Zend_Acl_Role_Interface) ? @_isAllowedRole : role,
|
798
|
+
# (@_isAllowedResource instanceof Zend_Acl_Resource_Interface) ? @_isAllowedResource : resource,
|
799
|
+
# @_isAllowedPrivilege
|
800
|
+
# )
|
801
|
+
end
|
802
|
+
|
803
|
+
if rule[:assert].nil? || assertion_value
|
804
|
+
rule[:type]
|
805
|
+
elsif resource != nil || role != nil || privilege != nil
|
806
|
+
nil
|
807
|
+
elsif rule[:type] == TYPE_ALLOW
|
808
|
+
TYPE_DENY
|
809
|
+
else
|
810
|
+
TYPE_ALLOW
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
|
815
|
+
# Returns the rules associated with a Resource and a Role, or nil if no such rules exist
|
816
|
+
#
|
817
|
+
# If either resource or role is nil, this means that the rules returned are for all Resources or all Roles,
|
818
|
+
# respectively. Both can be nil to return the default rule set for all Resources and all Roles.
|
819
|
+
#
|
820
|
+
# If the create parameter is true, then a rule set is first created and then returned to the caller.
|
821
|
+
#
|
822
|
+
# @param Rend::Acl::Resource resource
|
823
|
+
# @param Rend::Acl::Role role
|
824
|
+
# @param boolean create
|
825
|
+
# @return array|nil
|
826
|
+
def _rules(resource = nil, role = nil, create = false)
|
827
|
+
type_hint! Rend::Acl::Resource, resource
|
828
|
+
type_hint! Rend::Acl::Role, role
|
829
|
+
|
830
|
+
if resource.nil?
|
831
|
+
visitor = @_rules[:all_resources]
|
832
|
+
else
|
833
|
+
if !@_rules[:by_resource_id].has_key?(resource.id)
|
834
|
+
return nil unless create
|
835
|
+
@_rules[:by_resource_id][resource.id] = {}
|
836
|
+
end
|
837
|
+
visitor = @_rules[:by_resource_id][resource.id]
|
838
|
+
end
|
839
|
+
|
840
|
+
if role.nil?
|
841
|
+
if !visitor.has_key?(:all_roles)
|
842
|
+
return nil unless create
|
843
|
+
visitor[:all_roles] = { :by_privilege_id => {} }
|
844
|
+
end
|
845
|
+
return visitor[:all_roles]
|
846
|
+
end
|
847
|
+
|
848
|
+
visitor[:by_role_id] = {} unless visitor.has_key?(:by_role_id)
|
849
|
+
|
850
|
+
unless visitor[:by_role_id].has_key?(role.id)
|
851
|
+
return nil unless create
|
852
|
+
visitor[:by_role_id][role.id] = {
|
853
|
+
:by_privilege_id => {},
|
854
|
+
:all_privileges => {:type => nil}
|
855
|
+
}
|
856
|
+
end
|
857
|
+
visitor[:by_role_id][role.id]
|
858
|
+
end
|
859
|
+
|
860
|
+
# Returns an array of registered roles.
|
861
|
+
#
|
862
|
+
# Note that this method does not return instances of registered roles,
|
863
|
+
# but only the role identifiers.
|
864
|
+
#
|
865
|
+
# @return array of registered roles
|
866
|
+
def roles
|
867
|
+
role_registry.roles.keys
|
868
|
+
end
|
869
|
+
|
870
|
+
# @return array of registered resources
|
871
|
+
def resources
|
872
|
+
@_resources.keys
|
873
|
+
end
|
874
|
+
end
|
875
|
+
end
|