authorize 0.0.1

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