authorize 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +20 -0
- data/README +155 -0
- data/Rakefile +25 -0
- data/TODO.txt +9 -0
- data/authorize.gemspec +25 -0
- data/generators/authorize/USAGE +8 -0
- data/generators/authorize/authorize_generator.rb +7 -0
- data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
- data/install.rb +1 -0
- data/lib/authorize.rb +2 -0
- data/lib/authorize/action_controller.rb +59 -0
- data/lib/authorize/action_view.rb +4 -0
- data/lib/authorize/active_record.rb +37 -0
- data/lib/authorize/bitmask.rb +84 -0
- data/lib/authorize/exceptions.rb +30 -0
- data/lib/authorize/graph.rb +4 -0
- data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
- data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
- data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
- data/lib/authorize/graph/directed_graph.rb +27 -0
- data/lib/authorize/graph/edge.rb +58 -0
- data/lib/authorize/graph/factory.rb +39 -0
- data/lib/authorize/graph/fixtures.rb +33 -0
- data/lib/authorize/graph/graph.rb +55 -0
- data/lib/authorize/graph/traverser.rb +89 -0
- data/lib/authorize/graph/undirected_graph.rb +14 -0
- data/lib/authorize/graph/vertex.rb +53 -0
- data/lib/authorize/permission.rb +97 -0
- data/lib/authorize/redis.rb +2 -0
- data/lib/authorize/redis/array.rb +36 -0
- data/lib/authorize/redis/base.rb +165 -0
- data/lib/authorize/redis/connection_manager.rb +88 -0
- data/lib/authorize/redis/connection_specification.rb +16 -0
- data/lib/authorize/redis/factory.rb +64 -0
- data/lib/authorize/redis/fixtures.rb +22 -0
- data/lib/authorize/redis/hash.rb +34 -0
- data/lib/authorize/redis/model_reference.rb +21 -0
- data/lib/authorize/redis/model_set.rb +19 -0
- data/lib/authorize/redis/set.rb +42 -0
- data/lib/authorize/redis/string.rb +17 -0
- data/lib/authorize/resource.rb +4 -0
- data/lib/authorize/resource_pool.rb +87 -0
- data/lib/authorize/role.rb +115 -0
- data/lib/authorize/test_helper.rb +42 -0
- data/lib/authorize/trustee.rb +4 -0
- data/lib/authorize/version.rb +3 -0
- data/rails/init.rb +5 -0
- data/tasks/authorize_tasks.rake +4 -0
- data/test/Rakefile +7 -0
- data/test/app/controllers/application_controller.rb +5 -0
- data/test/app/controllers/thingy_controller.rb +11 -0
- data/test/app/controllers/widgets_controller.rb +2 -0
- data/test/app/models/public.rb +14 -0
- data/test/app/models/user.rb +8 -0
- data/test/app/models/widget.rb +7 -0
- data/test/config/boot.rb +109 -0
- data/test/config/database.yml +25 -0
- data/test/config/environment.rb +28 -0
- data/test/config/environments/development.rb +4 -0
- data/test/config/environments/test.rb +0 -0
- data/test/config/initializers/mask.rb +1 -0
- data/test/config/initializers/redis.rb +8 -0
- data/test/config/routes.rb +5 -0
- data/test/db/.gitignore +1 -0
- data/test/db/schema.rb +26 -0
- data/test/log/.gitignore +2 -0
- data/test/public/javascripts/application.js +2 -0
- data/test/public/javascripts/controls.js +963 -0
- data/test/public/javascripts/dragdrop.js +972 -0
- data/test/public/javascripts/effects.js +1120 -0
- data/test/public/javascripts/prototype.js +4225 -0
- data/test/script/about +3 -0
- data/test/script/console +3 -0
- data/test/script/dbconsole +3 -0
- data/test/script/destroy +3 -0
- data/test/script/generate +3 -0
- data/test/script/performance/benchmarker +3 -0
- data/test/script/performance/profiler +3 -0
- data/test/script/performance/request +3 -0
- data/test/script/plugin +3 -0
- data/test/script/process/inspector +3 -0
- data/test/script/process/reaper +3 -0
- data/test/script/process/spawner +3 -0
- data/test/script/runner +3 -0
- data/test/script/server +3 -0
- data/test/test/fixtures/authorize/role_graph.yml +11 -0
- data/test/test/fixtures/permissions.yml +27 -0
- data/test/test/fixtures/redis/redis.yml +8 -0
- data/test/test/fixtures/redis/role_graph.yml +29 -0
- data/test/test/fixtures/roles.yml +28 -0
- data/test/test/fixtures/users.yml +12 -0
- data/test/test/fixtures/widgets.yml +12 -0
- data/test/test/functional/controller_class_test.rb +36 -0
- data/test/test/functional/controller_test.rb +46 -0
- data/test/test/test_helper.rb +35 -0
- data/test/test/unit/bitmask_test.rb +112 -0
- data/test/test/unit/fixture_test.rb +59 -0
- data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
- data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
- data/test/test/unit/graph_directed_graph_test.rb +66 -0
- data/test/test/unit/graph_edge_test.rb +53 -0
- data/test/test/unit/graph_graph_test.rb +50 -0
- data/test/test/unit/graph_traverser_test.rb +43 -0
- data/test/test/unit/graph_vertex_test.rb +57 -0
- data/test/test/unit/permission_test.rb +123 -0
- data/test/test/unit/redis_array_test.rb +60 -0
- data/test/test/unit/redis_connection_manager_test.rb +54 -0
- data/test/test/unit/redis_factory_test.rb +85 -0
- data/test/test/unit/redis_fixture_test.rb +18 -0
- data/test/test/unit/redis_hash_test.rb +43 -0
- data/test/test/unit/redis_model_reference_test.rb +39 -0
- data/test/test/unit/redis_set_test.rb +68 -0
- data/test/test/unit/redis_string_test.rb +25 -0
- data/test/test/unit/redis_test.rb +121 -0
- data/test/test/unit/resource_pool_test.rb +93 -0
- data/test/test/unit/resource_test.rb +33 -0
- data/test/test/unit/role_test.rb +143 -0
- data/test/test/unit/trustee_test.rb +35 -0
- data/test/tmp/.gitignore +2 -0
- data/uninstall.rb +1 -0
- metadata +319 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class ConnectionSpecification
|
4
|
+
attr_reader :db_spec
|
5
|
+
|
6
|
+
def initialize(db_spec)
|
7
|
+
@db_spec = db_spec
|
8
|
+
end
|
9
|
+
|
10
|
+
# Factory method returning a new connection to the database conforming to the specification
|
11
|
+
def connect!
|
12
|
+
::Redis.new(db_spec)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# A Factory is designed to help build relevant test fixtures in the context of a test. In order to preserve a high
|
4
|
+
# signal-to-noise ratio in the test, a factory needs to *concisely* build fixtures even at the expense of supporting
|
5
|
+
# cool features. As a result, index management and references to other values are the responsibility of the programmer.
|
6
|
+
class Factory
|
7
|
+
attr_reader :db
|
8
|
+
|
9
|
+
def self.build(ns = nil, &block)
|
10
|
+
self.new.tap do |f|
|
11
|
+
f.namespace(ns, &block) if ns
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(db = Base.db)
|
16
|
+
@namespace = nil
|
17
|
+
@db = db
|
18
|
+
end
|
19
|
+
|
20
|
+
def namespace(name, &block)
|
21
|
+
@old_namespace, @namespace = @namespace, subordinate_key(name)
|
22
|
+
if block_given?
|
23
|
+
self.instance_eval(&block)
|
24
|
+
@namespace = @old_namespace
|
25
|
+
else
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def string(name, value = "")
|
31
|
+
key = subordinate_key(name)
|
32
|
+
db.set(key, value)
|
33
|
+
namespace(name){yield} if block_given?
|
34
|
+
Redis::String.load(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash(name, value = {})
|
38
|
+
key = subordinate_key(name)
|
39
|
+
value.each {|k, v| db.hset(key, k, v)}
|
40
|
+
namespace(name){yield} if block_given?
|
41
|
+
Redis::Hash.load(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(name, value = ::Set[])
|
45
|
+
key = subordinate_key(name)
|
46
|
+
value.each {|v| db.sadd(key, v)}
|
47
|
+
namespace(name){yield} if block_given?
|
48
|
+
Redis::Set.load(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def array(name, value = [])
|
52
|
+
key = subordinate_key(name)
|
53
|
+
value.each {|v| db.rpush(key, v)}
|
54
|
+
namespace(name){yield} if block_given?
|
55
|
+
Redis::Array.load(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def subordinate_key(key)
|
60
|
+
Authorize::Redis::Base.subordinate_key(@namespace, key)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# Persist Ruby objects to Redis DB using natural type affinity
|
4
|
+
module Fixtures
|
5
|
+
def create_fixtures(db, pathname, flush = true)
|
6
|
+
db.flushdb if flush
|
7
|
+
fixtures = YAML.load(ERB.new(pathname.read).result)
|
8
|
+
fixtures.each do |node|
|
9
|
+
node.each_pair do |key, value|
|
10
|
+
case value
|
11
|
+
when ::Hash then value.each_pair {|k, v| db.hset(key, k, v)}
|
12
|
+
when ::Set then value.each {|v| db.sadd(key, v)}
|
13
|
+
when ::Array then value.each {|v| db.rpush(key, v)}
|
14
|
+
else db.set(key, value) # String, Fixnum, NilClass
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
module_function :create_fixtures
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class Hash < Base
|
4
|
+
undef to_a # In older versions of Ruby, Object#to_a is invoked and #method_missing is never called.
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
%w(none hash).include?(db.type(id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(k)
|
11
|
+
db.hget(id, k)
|
12
|
+
end
|
13
|
+
alias [] get
|
14
|
+
|
15
|
+
def set(k, v)
|
16
|
+
db.hset(id, k, v)
|
17
|
+
end
|
18
|
+
alias []= set
|
19
|
+
|
20
|
+
def merge(h)
|
21
|
+
return self if h.empty?
|
22
|
+
args = h.inject([]) do |m,(k,v)|
|
23
|
+
m << k
|
24
|
+
m << v
|
25
|
+
end
|
26
|
+
db.hmset(id, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def __getobj__
|
30
|
+
db.hgetall(id)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# Support foreign keys that reference Redis::Base-like models.
|
4
|
+
module ModelReference
|
5
|
+
# Load the model whose key is held in the given string key.
|
6
|
+
def load_reference(key, klass)
|
7
|
+
reference = klass.db.get(key)
|
8
|
+
reference && klass.load(reference)
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_reference(key, model)
|
12
|
+
if model
|
13
|
+
Authorize::Redis::String.db.set(key, model.id)
|
14
|
+
else
|
15
|
+
Authorize::Redis::String.db.del(key) && nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
module_function :load_reference, :set_reference
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# A persistent set of homomorphic Redis-like models
|
4
|
+
class ModelSet < Redis::Set
|
5
|
+
def initialize(klass)
|
6
|
+
super()
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
[:add, :delete, :include?].each do |m|
|
11
|
+
define_method(m) {|v| super(v.id)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def __getobj__
|
15
|
+
super.map{|eid| @klass.load(eid)}.to_set.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class Set < Base
|
4
|
+
undef to_a # In older versions of Ruby, Object#to_a is invoked and #method_missing is never called.
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
%w(none set).include?(db.type(id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(v)
|
11
|
+
db.sadd(id, v)
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(v)
|
15
|
+
add(v)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(v)
|
19
|
+
db.srem(id, v)
|
20
|
+
end
|
21
|
+
|
22
|
+
def include?(v)
|
23
|
+
db.sismember(id, v)
|
24
|
+
end
|
25
|
+
alias member? include?
|
26
|
+
|
27
|
+
def sample(n = 1)
|
28
|
+
return method_missing(:sample, n) unless n == 1
|
29
|
+
db.srandmember(id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def first(n = 1)
|
33
|
+
return method_missing(:first, n) unless n == 1
|
34
|
+
sample(n)
|
35
|
+
end
|
36
|
+
|
37
|
+
def __getobj__
|
38
|
+
db.smembers(id).to_set
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'set'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Authorize
|
6
|
+
# Arbitrate thread-safe access to a limited set of expensive and perishable objects
|
7
|
+
class ResourcePool
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@pool, :include?
|
11
|
+
def_delegators :@tokens, :empty?
|
12
|
+
|
13
|
+
attr_reader :num_waiting
|
14
|
+
|
15
|
+
# Create a new unfilled resource pool with a capacity of of at most max_size objects. The pool
|
16
|
+
# is lazily filled by the factory lambda.
|
17
|
+
def initialize(max_size, factory)
|
18
|
+
@factory = factory
|
19
|
+
@pool = []
|
20
|
+
@tokens = []
|
21
|
+
@monitor = Monitor.new
|
22
|
+
@tokens_cv = @monitor.new_cond
|
23
|
+
@num_waiting = 0
|
24
|
+
max_size.times {|i| @tokens.unshift(i)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
@pool.compact.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def available
|
32
|
+
@tokens.size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checkout an object from the pool. Arguments are passed unmolested to ConditionVariable#wait to manage timeouts.
|
36
|
+
def checkout(*args)
|
37
|
+
@monitor.synchronize do
|
38
|
+
until token = @tokens.pop
|
39
|
+
begin
|
40
|
+
@num_waiting += 1
|
41
|
+
raise "Timed out during checkout" unless @tokens_cv.wait(*args)
|
42
|
+
ensure
|
43
|
+
@num_waiting -= 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@pool[token] ||= @factory.call
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return an object to the pool
|
51
|
+
def checkin(obj)
|
52
|
+
@monitor.synchronize do
|
53
|
+
token = @pool.index(obj)
|
54
|
+
raise "#{obj} has not been checked out from this pool" unless token && !@tokens.include?(token)
|
55
|
+
@tokens.push(token) if token
|
56
|
+
@tokens_cv.signal
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Expire resources in inventory with the given block. Available (not reserved) pool members are
|
61
|
+
# yielded to the block. The object is expired (removed permanently from the pool) if the block
|
62
|
+
# returns a true-ish value.
|
63
|
+
def expire
|
64
|
+
@monitor.synchronize do
|
65
|
+
@tokens.each do |i|
|
66
|
+
next unless obj = @pool[i]
|
67
|
+
@pool[i] = nil if yield obj
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Freshen objects in inventory with the given block
|
73
|
+
def freshen
|
74
|
+
expire {|obj| yield(obj) ; return false}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove all resources from the pool and revoke all tokens
|
78
|
+
# TODO: don't brutally/blindly revoke tokens -raise an exception or fire a callback, and address waiting threads.
|
79
|
+
def clear!
|
80
|
+
@monitor.synchronize do
|
81
|
+
@pool.each_index {|i| @tokens << i}
|
82
|
+
@tokens.uniq!
|
83
|
+
@pool.clear
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'authorize/redis'
|
2
|
+
|
3
|
+
class Authorize::Role < ActiveRecord::Base
|
4
|
+
set_table_name 'authorize_roles'
|
5
|
+
belongs_to :resource, :polymorphic => true
|
6
|
+
has_many :permissions, :class_name => "Authorize::Permission", :dependent => :delete_all
|
7
|
+
validates_uniqueness_of :name, :scope => [:resource_type, :resource_id]
|
8
|
+
validates_uniqueness_of :relation, :scope => [:resource_type, :resource_id]
|
9
|
+
after_create :create_vertex
|
10
|
+
before_destroy :destroy_vertex
|
11
|
+
|
12
|
+
named_scope :as, lambda{|relation| {:conditions => {:relation => relation}}}
|
13
|
+
named_scope :identity, {:conditions => {:relation => nil}}
|
14
|
+
|
15
|
+
GRAPH_ID = Authorize::Graph::DirectedAcyclicGraph.subordinate_key(Authorize::Role, 'graph')
|
16
|
+
VERTICES_ID_PREFIX = Authorize::Graph::DirectedAcyclicGraph.subordinate_key(Authorize::Role, 'vertices')
|
17
|
+
|
18
|
+
def self.const_missing(const)
|
19
|
+
if global_role = scoped(:conditions => {:resource_type => nil, :resource_id => nil}).find_by_relation(const.to_s)
|
20
|
+
const_set(const, global_role)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.graph
|
27
|
+
@graph ||= Authorize::Graph::DirectedGraph.load(GRAPH_ID).tap do |g|
|
28
|
+
g.vertex_namespace = VERTICES_ID_PREFIX
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_vertex
|
33
|
+
self.class.graph.vertex(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy_vertex
|
37
|
+
vertex.destroy
|
38
|
+
end
|
39
|
+
|
40
|
+
# Link from this role's vertex to other's vertex in the system role graph. This role becomes the parent.
|
41
|
+
def link(other)
|
42
|
+
self.class.graph.join(nil, vertex, other.vertex)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Unlink this role's vertex from other's vertex in the system role graph.
|
46
|
+
def unlink(other)
|
47
|
+
self.class.graph.disjoin(vertex, other.vertex)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates or updates the unique permission for a given resource to have the given modes
|
51
|
+
# Example: public.may(:list, :read, widget)
|
52
|
+
def may(*args)
|
53
|
+
p = permissions.for(args.pop).find_or_initialize_by_role_id(id) # need a #find_or_initialize_by_already_specified_scope
|
54
|
+
p.mask += Authorize::Permission::Mask[*args]
|
55
|
+
p.save
|
56
|
+
p.mask.complete
|
57
|
+
end
|
58
|
+
|
59
|
+
# Updates or deletes the unique permission for a given resource to not have the given modes
|
60
|
+
# Example: public.may_not(:update, widget)
|
61
|
+
def may_not(*args)
|
62
|
+
p = permissions.for(args.pop).first
|
63
|
+
return Authorize::Permission::Mask[] unless p
|
64
|
+
p.mask -= Authorize::Permission::Mask[*args].complete
|
65
|
+
p.mask.empty? ? p.destroy : p.save
|
66
|
+
p.mask.complete
|
67
|
+
end
|
68
|
+
|
69
|
+
# Test if all given modes are permitted for the given resource
|
70
|
+
def may?(*args)
|
71
|
+
return false unless p = permissions.for(args.pop).first
|
72
|
+
mask = Authorize::Permission::Mask[*args].complete
|
73
|
+
mask.subset?(p.mask)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Test if none of the given modes are permitted for the given resource
|
77
|
+
def may_not?(*args)
|
78
|
+
return true unless p = permissions.for(args.pop).first
|
79
|
+
mask = Authorize::Permission::Mask[*args].complete
|
80
|
+
(mask & p.mask).empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
(name || "%s") % resource rescue "!! INVALID ROLE NAME !!"
|
85
|
+
end
|
86
|
+
|
87
|
+
def vertex
|
88
|
+
raise 'Not possible to dereference vertex for an unpersisted role' unless id
|
89
|
+
@vertex ||= self.class.graph.vertex_by_name(id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def roles
|
93
|
+
ids = traverser.map{|v| v.id.slice(/#{VERTICES_ID_PREFIX}::(\d+)/, 1)}
|
94
|
+
self.class.find(ids).to_set
|
95
|
+
end
|
96
|
+
|
97
|
+
def descendants
|
98
|
+
roles.delete(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
def ancestors
|
102
|
+
ids = reverse_traverser.map{|v| v.id.slice(/#{VERTICES_ID_PREFIX}::(\d+)/, 1)}
|
103
|
+
ids -= [id.to_s]
|
104
|
+
self.class.find(ids).to_set
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def traverser
|
109
|
+
@traverser ||= Authorize::Graph::DirectedAcyclicGraphTraverser.traverse(vertex)
|
110
|
+
end
|
111
|
+
|
112
|
+
def reverse_traverser
|
113
|
+
@reverse_traverser ||= Authorize::Graph::DirectedAcyclicGraphReverseTraverser.traverse(vertex)
|
114
|
+
end
|
115
|
+
end
|