aclatraz 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ # == Dinner example.
2
+ #
3
+ # Don't forget to initialize ACLatraz datastore and your ActiveRecord
4
+ # connection with database.
5
+
6
+ class Person < ActiveRecord::Base
7
+ include Aclatraz::Suspect
8
+ end
9
+
10
+ class Dinner < ActiveRecord::Base
11
+ end
12
+
13
+ class Kitchen
14
+ include Aclatraz::Guard
15
+
16
+ suspects "@person" do
17
+ deny all
18
+ action :eat_dinner do
19
+ allow :hungry
20
+ end
21
+ action :get_dinner do
22
+ allow :servant_at => Dinner
23
+ allow :creator_of => "@dinner"
24
+ end
25
+ action :prepare_dinner do
26
+ allow :chef
27
+ end
28
+ end
29
+
30
+ def initialize(person)
31
+ @person = person
32
+ end
33
+
34
+ def prepare_dinner
35
+ guard! :prepare_dinner
36
+ @dinner = Dinner.create
37
+ @person.is.creator_of!(@dinner)
38
+ end
39
+
40
+ def get_dinner(id)
41
+ @dinner = Dinner.find(id)
42
+ guard! :get_dinner
43
+ end
44
+
45
+ def eat_dinner(id)
46
+ @dinner = Dinner.find(id)
47
+ guard! :eat_dinner
48
+ @dinner.destroy
49
+ end
50
+ end
51
+
52
+ # Usage...
53
+
54
+ person = Person.find(10)
55
+ kitchen = Kitchen.new(person)
56
+
57
+ kitchen.prepare_dinner # => Access denied
58
+ person.is.chef!
59
+ kitchen.prepare_dinner # => Ok
60
+
61
+ kitchen.get_dinner(10) # => Ok, he creates the @dinner
62
+ person.is_not.creator_of!(Dinner.find(10))
63
+ kitchen.get_dinner(10) # => Access denied
64
+ person.is.servant_at!(Dinner)
65
+ kitchen.get_dinner(10) # => Ok
66
+
67
+ kitchen.eat_dinner(10) # => Access denied, he is not hungry!
68
+ person.is.hungry!
69
+ kitchen.eat_dinner(10) # => Ok, enjoy your meal :)
70
+ person.is_not.hungry!
71
+
data/lib/aclatraz.rb CHANGED
@@ -7,24 +7,46 @@ require 'aclatraz/guard'
7
7
  require 'aclatraz/suspect'
8
8
 
9
9
  module Aclatraz
10
+ # Raised when suspect don't have permission to execute action
10
11
  class AccessDenied < Exception; end
12
+
13
+ # Raised when suspect specified in guarded class is invalid
11
14
  class InvalidSuspect < ArgumentError; end
15
+
16
+ # Raised when invalid permission is set in ACL
12
17
  class InvalidPermission < ArgumentError; end
18
+
19
+ # Raised when try to initialize invalid datastore
13
20
  class InvalidStore < ArgumentError; end
21
+
22
+ # Raised when datastore is not initialized when managing permission
14
23
  class StoreNotInitialized < Exception; end
15
24
 
25
+ # Raised when try to guard class without any ACL defined
26
+ class UndefinedAccessControlList < Exception; end
27
+
16
28
  extend Helpers
29
+
30
+ # Initialize Aclatraz system with given datastore.
31
+ #
32
+ # Aclatraz.init :redis, "redis://localhost:6379/0"
33
+ # Aclatraz.init :tokyocabinet, "./permissions.tch"
34
+ # Aclatraz.init MyCustomDatastore, :option => 1 # ...
35
+ def self.init(store, *args)
36
+ store = eval("Aclatraz::Store::#{camelize(store.to_s)}") unless store.is_a?(Class)
37
+ @store = store.new(*args)
38
+ rescue NameError
39
+ raise InvalidStore, "The #{store.inspect} ACL store is not defined!"
40
+ end
41
+
42
+ # Returns current datastore object, or raises +StoreNotInitialized+ when
43
+ # +init+ method wasn't called before.
44
+ def self.store
45
+ @store or raise StoreNotInitialized, "ACLatraz is not initialized!"
46
+ end
17
47
 
18
- def self.store(klass=nil, *args)
19
- if klass
20
- begin
21
- klass = eval("Aclatraz::Store::#{camelize(klass.to_s)}") unless klass.is_a?(Class)
22
- @store = klass.new(*args)
23
- rescue NameError
24
- raise InvalidStore, "The #{klass.inspect} ACL store is not defined!"
25
- end
26
- else
27
- @store or raise StoreNotInitialized, "ACL store is not initialized!"
28
- end
48
+ # Access control lists fof all classes protected by Aclatraz.
49
+ def self.acl
50
+ @acl ||= {}
29
51
  end
30
- end
52
+ end # Aclatraz
data/lib/aclatraz/acl.rb CHANGED
@@ -1,46 +1,113 @@
1
1
  module Aclatraz
2
2
  class ACL
3
3
  class Action
4
+ # All permissions defined in this action.
4
5
  attr_reader :permissions
5
6
 
6
- def initialize(parent, &block)
7
+ def initialize(parent, &block) # :nodoc:
7
8
  @parent = parent
8
9
  @permissions = Dictionary.new
9
- instance_eval(&block)
10
+ instance_eval(&block) if block_given?
10
11
  end
11
12
 
13
+ # Add permission for objects which have given role.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # allow :admin
18
+ # allow :owner_of => "object"
19
+ # allow :owner_of => :object
20
+ # allow :owner_of => object
21
+ # allow :manager_of => ClassName
22
+ # allow all
12
23
  def allow(permission)
13
24
  @permissions[permission] = true
14
25
  end
15
26
 
27
+ # Add permission for objects which don't have given role.
28
+ #
29
+ # ==== Examples
30
+ #
31
+ # deny :admin
32
+ # deny :owner_of => "object"
33
+ # deny :owner_of => :object
34
+ # deny :owner_of => object
35
+ # deny :manager_of => ClassName
36
+ # deny all
16
37
  def deny(permission)
17
38
  @permissions[permission] = false
18
39
  end
19
40
 
41
+ # Syntactic sugar for aliasing all permissions.
42
+ #
43
+ # ==== Examples
44
+ #
45
+ # allow all
46
+ # deny all
20
47
  def all
21
48
  true
22
49
  end
23
50
 
51
+ # See <tt>Aclatraz::ACL#on</tt>.
24
52
  def on(*args, &block)
25
53
  @parent.on(*args, &block)
26
54
  end
27
- end
55
+
56
+ def clone(parent) # :nodoc:
57
+ cloned = self.class.new(parent)
58
+ cloned.instance_variable_set("@permissions", Hash.new(permissions))
59
+ cloned
60
+ end
61
+ end # Action
28
62
 
63
+ # All actions defined in current ACL.
29
64
  attr_reader :actions
30
65
 
31
- def initialize(&block)
66
+ # Current suspected object.
67
+ attr_accessor :suspect
68
+
69
+ def initialize(suspect, &block) # :nodoc:
32
70
  @actions = {}
71
+ @suspect = suspect
72
+ evaluate(&block) if block_given?
73
+ end
74
+
75
+ # Evaluates given block in default action.
76
+ #
77
+ # ==== Example
78
+ #
79
+ # evaluate do
80
+ # allow :foo
81
+ # deny :bar
82
+ # ...
83
+ # end
84
+ def evaluate(&block)
33
85
  on(:_, &block)
34
86
  end
35
87
 
88
+ # List of permissions defined in default action.
36
89
  def permissions
37
90
  @actions[:_] ? @actions[:_].permissions : Dictionary.new
38
91
  end
39
92
 
93
+ # Syntactic sugar for actions <tt>actions[action]</tt>.
40
94
  def [](action)
41
- @actions[action]
95
+ actions[action]
42
96
  end
43
97
 
98
+ # Defines given action with permissions described in evaluated block.
99
+ #
100
+ # ==== Examples
101
+ #
102
+ # suspects do
103
+ # on :create do
104
+ # deny all
105
+ # allow :admin
106
+ # end
107
+ # on :delete do
108
+ # allow :owner_of => "object"
109
+ # end
110
+ # end
44
111
  def on(action, &block)
45
112
  raise ArgumentError, "No block given" unless block_given?
46
113
  if @actions.key?(action)
@@ -49,5 +116,13 @@ module Aclatraz
49
116
  @actions[action] = Action.new(self, &block)
50
117
  end
51
118
  end
52
- end
53
- end
119
+
120
+ def clone(&block) # :nodoc:
121
+ actions = Hash[*self.actions.map {|k,v| [k, v.clone(self)] }.flatten]
122
+ cloned = self.class.new(suspect)
123
+ cloned.instance_variable_set("@actions", actions)
124
+ cloned.evaluate(&block)
125
+ cloned
126
+ end
127
+ end # ACL
128
+ end # Aclatraz
@@ -1,87 +1,169 @@
1
1
  module Aclatraz
2
2
  module Guard
3
- def self.included(base)
3
+ def self.included(base) # :nodoc:
4
4
  base.send :extend, ClassMethods
5
5
  base.send :include, InstanceMethods
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- attr_reader :acl_suspect
10
- attr_reader :acl_permissions
11
-
12
- def suspects(name, &block)
13
- @acl_suspect = name
14
- @acl_permissions = ACL.new(&block)
9
+ def acl_guard? # :nodoc:
10
+ true
11
+ end
12
+
13
+ # Define access controll list for current class.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # suspects :foo do # foo method result will be treated as suspect
18
+ # deny all
19
+ # allow :admin
20
+ #
21
+ # on :create do
22
+ # allow :manager
23
+ # allow :manager_of => ClassName
24
+ # end
25
+ #
26
+ # on :edit do
27
+ # # only @object_name instance variable owner is allowed to edit it.
28
+ # allow :owner_of => "object_name"
29
+ # end
30
+ # end
31
+ #
32
+ # # When called second time don't have to specify suspected object.
33
+ # suspects do
34
+ # allow :manager
35
+ # end
36
+ def suspects(suspect=nil, &block)
37
+ if acl = Aclatraz.acl[name]
38
+ acl.suspect = suspect if suspect
39
+ acl.evaluate(&block)
40
+ elsif superclass.respond_to?(:acl_guard?) && acl = Aclatraz.acl[superclass.name]
41
+ Aclatraz.acl[name] = acl.clone(&block)
42
+ else
43
+ Aclatraz.acl[name] = Aclatraz::ACL.new(suspect, &block)
44
+ end
15
45
  end
16
46
  alias_method :access_control, :suspects
17
- end
47
+ end # ClassMethods
18
48
 
19
49
  module InstanceMethods
20
- def current_suspect
21
- case self.class.acl_suspect
22
- when Symbol
23
- @current_suspect ||= send(self.class.acl_suspect)
24
- when String
25
- @current_suspect ||= instance_variable_get("@#{self.class.acl_suspect}")
26
- else
27
- @current_suspect ||= self.class.acl_suspect
50
+ # Returns suspected object.
51
+ #
52
+ # * when suspect name is a String then will return instance variable
53
+ # * when suspect name is a Symbol then will be returned value of instance method
54
+ # * otherwise suspect name will be treated as suspect object.
55
+ #
56
+ # ==== Examples
57
+ #
58
+ # class Bar
59
+ # suspects(:foo) { ... }
60
+ # def foo; @foo = Foo.new; end
61
+ # end
62
+ # Bar.new.suspect.class # => Foo
63
+ #
64
+ # class Bla
65
+ # suspects("foo") { ... }
66
+ # def init; @foo = Foo.new; end
67
+ # end
68
+ # Bla.new.suspect.class # => Foo
69
+ #
70
+ # class Spam
71
+ # foo = Foo.new
72
+ # suspects(foo) { ... }
73
+ # end
74
+ # Spam.new.suspect.class # => Foo
75
+ #
76
+ # You can also override this method in your protected class, and skip
77
+ # passing arguments to +#suspects+ method, eg.
78
+ #
79
+ # class Eggs
80
+ # suspects { ... }
81
+ # def suspect; @foo = Foo.new; end
82
+ # end
83
+ def suspect
84
+ @suspect ||= if acl = Aclatraz.acl[self.class.name]
85
+ case acl.suspect
86
+ when Symbol
87
+ send(acl.suspect)
88
+ when String
89
+ instance_variable_get("@#{acl.suspect}")
90
+ else
91
+ acl.suspect
92
+ end
28
93
  end
29
94
  end
30
95
 
31
- def guard!(*actions)
32
- if current_suspect.respond_to?(:acl_suspect?)
33
- actions.unshift(:_)
34
- authorized = false
35
- permissions = Dictionary.new
36
-
37
- actions.each do |action|
38
- self.class.acl_permissions.actions[action].permissions.each_pair do |key, value|
39
- permissions.delete(key)
40
- permissions.push(key, value)
41
- end
96
+ # Check if current suspect have permissions to execute following code.
97
+ # If suspect hasn't required permissions, or access for any of his roles
98
+ # is denied then raises +Aclatraz::AccessDenied+ error. You can also specify
99
+ # additional permission inside given block:
100
+ #
101
+ # guard! do
102
+ # deny :foo
103
+ # allow :bar
104
+ # end
105
+ def guard!(*actions, &block)
106
+ acl = Aclatraz.acl[self.class.name] or raise UndefinedAccessControlList, "No ACL for #{self.class.name} class"
107
+ suspect.respond_to?(:acl_suspect?) or raise Aclatraz::InvalidSuspect, "Invalid ACL suspect: #{suspect.inspect}"
108
+ authorized = false
109
+ permissions = Dictionary.new
110
+ actions.unshift(:_)
111
+
112
+ if block_given?
113
+ aname = "#{__FILE__}:#{__LINE__}"
114
+ acl.on(aname, &block)
115
+ actions.push(aname)
116
+ end
117
+
118
+ actions.each do |action|
119
+ acl.actions[action].permissions.each_pair do |key, value|
120
+ permissions.delete(key)
121
+ permissions.push(key, value)
42
122
  end
43
-
44
- permissions.each do |permission, allow|
45
- has_permission = check_permission(permission)
46
- if permission == true
47
- authorized = allow ? true : false
48
- next
49
- end
50
- if allow
51
- authorized ||= has_permission
52
- else
53
- authorized = false if has_permission
54
- end
123
+ end
124
+
125
+ permissions.each do |permission, allow|
126
+ if permission == true
127
+ authorized = allow ? true : false
128
+ next
129
+ end
130
+ if allow
131
+ authorized ||= assert_permission(permission)
132
+ else
133
+ authorized = false if assert_permission(permission)
55
134
  end
56
-
57
- raise Aclatraz::AccessDenied unless authorized
58
- true
59
- else
60
- raise Aclatraz::InvalidSuspect
61
135
  end
136
+
137
+ authorized or raise Aclatraz::AccessDenied, "Access Denied"
138
+ return true
62
139
  end
63
140
  alias_method :authorize!, :guard!
64
141
 
65
- def check_permission(permission)
142
+ # Check if current suspect has given permissions.
143
+ #
144
+ # ==== Examples
145
+ #
146
+ # assert_permission(:admin)
147
+ # assert_permission(:manager_of => ClassName)
148
+ # assert_permission(:owner_of => "object")
149
+ def assert_permission(permission)
66
150
  case permission
67
151
  when String, Symbol, true
68
- current_suspect.has_role?(permission)
152
+ suspect.roles.has?(permission)
69
153
  when Hash
70
154
  permission.each do |role, object|
71
- case object
72
- when String
73
- object = "@#{object}" unless object[0] == "@"
74
- object = instance_variable_get(object)
75
- when Symbol
155
+ if object.is_a?(String)
156
+ object = instance_variable_get(object[0] ? "@#{object}" : object)
157
+ elsif object.is_a?(Symbol)
76
158
  object = send(object)
77
159
  end
78
- return true if current_suspect.has_role?(role, object)
160
+ return true if suspect.roles.has?(role, object)
79
161
  end
80
- false
162
+ return false
81
163
  else
82
- raise Aclatraz::InvalidPermission
164
+ raise Aclatraz::InvalidPermission, "Invalid ACL permission: #{permission.inspect}"
83
165
  end
84
166
  end
85
- end
86
- end
87
- end
167
+ end # InstanceMethods
168
+ end # Guard
169
+ end # Aclatraz