rend-acl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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