authorize 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.
Files changed (124) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +20 -0
  5. data/README +155 -0
  6. data/Rakefile +25 -0
  7. data/TODO.txt +9 -0
  8. data/authorize.gemspec +25 -0
  9. data/generators/authorize/USAGE +8 -0
  10. data/generators/authorize/authorize_generator.rb +7 -0
  11. data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
  12. data/install.rb +1 -0
  13. data/lib/authorize.rb +2 -0
  14. data/lib/authorize/action_controller.rb +59 -0
  15. data/lib/authorize/action_view.rb +4 -0
  16. data/lib/authorize/active_record.rb +37 -0
  17. data/lib/authorize/bitmask.rb +84 -0
  18. data/lib/authorize/exceptions.rb +30 -0
  19. data/lib/authorize/graph.rb +4 -0
  20. data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
  21. data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
  22. data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
  23. data/lib/authorize/graph/directed_graph.rb +27 -0
  24. data/lib/authorize/graph/edge.rb +58 -0
  25. data/lib/authorize/graph/factory.rb +39 -0
  26. data/lib/authorize/graph/fixtures.rb +33 -0
  27. data/lib/authorize/graph/graph.rb +55 -0
  28. data/lib/authorize/graph/traverser.rb +89 -0
  29. data/lib/authorize/graph/undirected_graph.rb +14 -0
  30. data/lib/authorize/graph/vertex.rb +53 -0
  31. data/lib/authorize/permission.rb +97 -0
  32. data/lib/authorize/redis.rb +2 -0
  33. data/lib/authorize/redis/array.rb +36 -0
  34. data/lib/authorize/redis/base.rb +165 -0
  35. data/lib/authorize/redis/connection_manager.rb +88 -0
  36. data/lib/authorize/redis/connection_specification.rb +16 -0
  37. data/lib/authorize/redis/factory.rb +64 -0
  38. data/lib/authorize/redis/fixtures.rb +22 -0
  39. data/lib/authorize/redis/hash.rb +34 -0
  40. data/lib/authorize/redis/model_reference.rb +21 -0
  41. data/lib/authorize/redis/model_set.rb +19 -0
  42. data/lib/authorize/redis/set.rb +42 -0
  43. data/lib/authorize/redis/string.rb +17 -0
  44. data/lib/authorize/resource.rb +4 -0
  45. data/lib/authorize/resource_pool.rb +87 -0
  46. data/lib/authorize/role.rb +115 -0
  47. data/lib/authorize/test_helper.rb +42 -0
  48. data/lib/authorize/trustee.rb +4 -0
  49. data/lib/authorize/version.rb +3 -0
  50. data/rails/init.rb +5 -0
  51. data/tasks/authorize_tasks.rake +4 -0
  52. data/test/Rakefile +7 -0
  53. data/test/app/controllers/application_controller.rb +5 -0
  54. data/test/app/controllers/thingy_controller.rb +11 -0
  55. data/test/app/controllers/widgets_controller.rb +2 -0
  56. data/test/app/models/public.rb +14 -0
  57. data/test/app/models/user.rb +8 -0
  58. data/test/app/models/widget.rb +7 -0
  59. data/test/config/boot.rb +109 -0
  60. data/test/config/database.yml +25 -0
  61. data/test/config/environment.rb +28 -0
  62. data/test/config/environments/development.rb +4 -0
  63. data/test/config/environments/test.rb +0 -0
  64. data/test/config/initializers/mask.rb +1 -0
  65. data/test/config/initializers/redis.rb +8 -0
  66. data/test/config/routes.rb +5 -0
  67. data/test/db/.gitignore +1 -0
  68. data/test/db/schema.rb +26 -0
  69. data/test/log/.gitignore +2 -0
  70. data/test/public/javascripts/application.js +2 -0
  71. data/test/public/javascripts/controls.js +963 -0
  72. data/test/public/javascripts/dragdrop.js +972 -0
  73. data/test/public/javascripts/effects.js +1120 -0
  74. data/test/public/javascripts/prototype.js +4225 -0
  75. data/test/script/about +3 -0
  76. data/test/script/console +3 -0
  77. data/test/script/dbconsole +3 -0
  78. data/test/script/destroy +3 -0
  79. data/test/script/generate +3 -0
  80. data/test/script/performance/benchmarker +3 -0
  81. data/test/script/performance/profiler +3 -0
  82. data/test/script/performance/request +3 -0
  83. data/test/script/plugin +3 -0
  84. data/test/script/process/inspector +3 -0
  85. data/test/script/process/reaper +3 -0
  86. data/test/script/process/spawner +3 -0
  87. data/test/script/runner +3 -0
  88. data/test/script/server +3 -0
  89. data/test/test/fixtures/authorize/role_graph.yml +11 -0
  90. data/test/test/fixtures/permissions.yml +27 -0
  91. data/test/test/fixtures/redis/redis.yml +8 -0
  92. data/test/test/fixtures/redis/role_graph.yml +29 -0
  93. data/test/test/fixtures/roles.yml +28 -0
  94. data/test/test/fixtures/users.yml +12 -0
  95. data/test/test/fixtures/widgets.yml +12 -0
  96. data/test/test/functional/controller_class_test.rb +36 -0
  97. data/test/test/functional/controller_test.rb +46 -0
  98. data/test/test/test_helper.rb +35 -0
  99. data/test/test/unit/bitmask_test.rb +112 -0
  100. data/test/test/unit/fixture_test.rb +59 -0
  101. data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
  102. data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
  103. data/test/test/unit/graph_directed_graph_test.rb +66 -0
  104. data/test/test/unit/graph_edge_test.rb +53 -0
  105. data/test/test/unit/graph_graph_test.rb +50 -0
  106. data/test/test/unit/graph_traverser_test.rb +43 -0
  107. data/test/test/unit/graph_vertex_test.rb +57 -0
  108. data/test/test/unit/permission_test.rb +123 -0
  109. data/test/test/unit/redis_array_test.rb +60 -0
  110. data/test/test/unit/redis_connection_manager_test.rb +54 -0
  111. data/test/test/unit/redis_factory_test.rb +85 -0
  112. data/test/test/unit/redis_fixture_test.rb +18 -0
  113. data/test/test/unit/redis_hash_test.rb +43 -0
  114. data/test/test/unit/redis_model_reference_test.rb +39 -0
  115. data/test/test/unit/redis_set_test.rb +68 -0
  116. data/test/test/unit/redis_string_test.rb +25 -0
  117. data/test/test/unit/redis_test.rb +121 -0
  118. data/test/test/unit/resource_pool_test.rb +93 -0
  119. data/test/test/unit/resource_test.rb +33 -0
  120. data/test/test/unit/role_test.rb +143 -0
  121. data/test/test/unit/trustee_test.rb +35 -0
  122. data/test/tmp/.gitignore +2 -0
  123. data/uninstall.rb +1 -0
  124. 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,17 @@
1
+ module Authorize
2
+ module Redis
3
+ class String < Base
4
+ def valid?
5
+ %w(none string).include?(db.type(id))
6
+ end
7
+
8
+ def __getobj__
9
+ db.get(id)
10
+ end
11
+
12
+ def set(v)
13
+ db.set(id, v)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module Authorize
2
+ module Resource
3
+ end
4
+ 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