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/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