annotation_security 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/CHANGELOG +2 -0
  2. data/HOW-TO +261 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README +39 -0
  5. data/Rakefile +56 -0
  6. data/assets/app/helpers/annotation_security_helper.rb +9 -0
  7. data/assets/config/initializers/annotation_security.rb +12 -0
  8. data/assets/config/security/relations.rb +20 -0
  9. data/assets/config/security/rights.yml +16 -0
  10. data/assets/vendor/plugins/annotation_security/init.rb +14 -0
  11. data/bin/annotation_security +8 -0
  12. data/lib/annotation_security/exceptions.rb +125 -0
  13. data/lib/annotation_security/exec.rb +189 -0
  14. data/lib/annotation_security/filters.rb +38 -0
  15. data/lib/annotation_security/includes/action_controller.rb +144 -0
  16. data/lib/annotation_security/includes/active_record.rb +28 -0
  17. data/lib/annotation_security/includes/helper.rb +215 -0
  18. data/lib/annotation_security/includes/resource.rb +85 -0
  19. data/lib/annotation_security/includes/role.rb +31 -0
  20. data/lib/annotation_security/includes/user.rb +27 -0
  21. data/lib/annotation_security/manager/policy_factory.rb +30 -0
  22. data/lib/annotation_security/manager/policy_manager.rb +80 -0
  23. data/lib/annotation_security/manager/relation_loader.rb +273 -0
  24. data/lib/annotation_security/manager/resource_manager.rb +36 -0
  25. data/lib/annotation_security/manager/right_loader.rb +88 -0
  26. data/lib/annotation_security/model_observer.rb +61 -0
  27. data/lib/annotation_security/policy/abstract_policy.rb +345 -0
  28. data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
  29. data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
  30. data/lib/annotation_security/policy/rule.rb +340 -0
  31. data/lib/annotation_security/policy/rule_set.rb +139 -0
  32. data/lib/annotation_security/rails.rb +39 -0
  33. data/lib/annotation_security/user_wrapper.rb +74 -0
  34. data/lib/annotation_security/utils.rb +142 -0
  35. data/lib/annotation_security.rb +98 -0
  36. data/lib/extensions/action_controller.rb +33 -0
  37. data/lib/extensions/active_record.rb +35 -0
  38. data/lib/extensions/filter.rb +134 -0
  39. data/lib/extensions/object.rb +11 -0
  40. data/lib/security_context.rb +551 -0
  41. data/spec/annotation_security/exceptions_spec.rb +17 -0
  42. data/spec/annotation_security/includes/helper_spec.rb +82 -0
  43. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
  44. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
  45. data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
  46. data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
  47. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
  48. data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
  49. data/spec/annotation_security/policy/rule_spec.rb +78 -0
  50. data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
  51. data/spec/annotation_security/security_context_spec.rb +78 -0
  52. data/spec/annotation_security/utils_spec.rb +74 -0
  53. data/spec/helper/test_controller.rb +66 -0
  54. data/spec/helper/test_helper.rb +5 -0
  55. data/spec/helper/test_relations.rb +7 -0
  56. data/spec/helper/test_resource.rb +39 -0
  57. data/spec/helper/test_rights.yml +5 -0
  58. data/spec/helper/test_role.rb +22 -0
  59. data/spec/helper/test_user.rb +32 -0
  60. data/spec/rails_stub.rb +38 -0
  61. data/spec/spec_helper.rb +43 -0
  62. metadata +157 -0
@@ -0,0 +1,31 @@
1
+ #
2
+ # = lib/annotation_security/includes/role.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::Role
6
+ #
7
+ # This module should be included by all role classes
8
+ # to enable full support of all features.
9
+ #
10
+ # A role class is a domain class that represents user roles
11
+ # and does not extend the user class. It should have the method #user that
12
+ # returns the user object it belongs to.
13
+ #
14
+ module AnnotationSecurity::Role
15
+
16
+ # Returns true if this belongs to the user given as parameter.
17
+ #
18
+ # Required to have a common interface with AnnotationSecurity::User.
19
+ #
20
+ def is_user?(user)
21
+ self.user == user
22
+ end
23
+
24
+ # If +obj+ is a UserWrapper, extract the role before comparing
25
+ #
26
+ def ==(obj)
27
+ obj = obj.__role__ if obj.is_a? AnnotationSecurity::UserWrapper
28
+ super(obj)
29
+ end
30
+
31
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # = lib/annotation_security/includes/user.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::User
6
+ #
7
+ # This module should be included by the user domain class to
8
+ # enable full support of all features.
9
+ #
10
+ module AnnotationSecurity::User
11
+
12
+ # Returns true if this is the user given as parameter.
13
+ #
14
+ # Required to have a common interface with AnnotationSecurity::Role.
15
+ #
16
+ def is_user?(user)
17
+ self == user
18
+ end
19
+
20
+ # If +obj+ is a UserWrapper, extract the user before comparing
21
+ #
22
+ def ==(obj)
23
+ obj = obj.__user__ if obj.is_a? AnnotationSecurity::UserWrapper
24
+ super(obj)
25
+ end
26
+
27
+ end
@@ -0,0 +1,30 @@
1
+ #
2
+ # = lib/annotation_security/manager/policy_factory.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::PolicyFactory
6
+ # Builds the policy classes.
7
+ #
8
+ class AnnotationSecurity::PolicyFactory # :nodoc:
9
+
10
+ def initialize(resource_class)
11
+ @klass = AnnotationSecurity::AbstractPolicy.new_subclass(resource_class)
12
+ end
13
+
14
+ def policy_class
15
+ @klass
16
+ end
17
+
18
+ def add_rule(symbol,*args,&block)
19
+ @klass.add_rule(symbol,*args,&block)
20
+ end
21
+
22
+ def create_policy(*args)
23
+ @klass.new(*args)
24
+ end
25
+
26
+ def reset
27
+ @klass.reset
28
+ end
29
+
30
+ end
@@ -0,0 +1,80 @@
1
+ #
2
+ # = lib/annotation_security/manager/policy_manager.rb
3
+ #
4
+ require 'yaml'
5
+
6
+ # = AnnotationSecurity::PolicyManager
7
+ #
8
+ # Manages loading and creation of all policy classes.
9
+ #
10
+ class AnnotationSecurity::PolicyManager # :nodoc:
11
+
12
+ # Get the policy factory for a resource class
13
+ def self.policy_factory(resource_type) # :nodoc:
14
+ policy_factories[resource_type.to_sym]
15
+ end
16
+
17
+ # Creates a policy object for a user and a resource type
18
+ #
19
+ # ==== Example
20
+ #
21
+ # picture = Picture.find_by_id(params[:picture])
22
+ # policy = PolicyManager.get_policy(:picture,@current_user)
23
+ # policy.allowed? :show, picture # => true or false
24
+ #
25
+ def self.create_policy(resource_type,*args)
26
+ policy_factory(resource_type).create_policy(*args)
27
+ end
28
+
29
+ def self.policy_class(resource_class) # :nodoc:
30
+ policy_factory(resource_class).policy_class
31
+ end
32
+
33
+ def self.config_files # :nodoc:
34
+ @files ||= []
35
+ end
36
+
37
+ # Adds a file that contains security configurations
38
+ # * +f+ file name
39
+ # * +ext+ 'yml' or 'rb'
40
+ def self.add_file(f,ext) # :nodoc:
41
+ unless config_files.include? [f,ext]
42
+ config_files.push [f,ext]
43
+ load_file(f,ext)
44
+ end
45
+ end
46
+
47
+ def self.reset
48
+ policy_factories.each_value(&:reset)
49
+ config_files.each { |f,ext| load_file(f,ext) }
50
+ end
51
+
52
+ private
53
+
54
+ def self.load_file(f,ext)
55
+ fname = get_file_name(f,ext)
56
+ case ext
57
+ when 'yml'
58
+ AnnotationSecurity::RightLoader.define_rights(YAML.load_file(fname))
59
+ when 'rb'
60
+ load fname
61
+ end
62
+ end
63
+
64
+ SEARCH_PATH = ['', RAILS_ROOT, RAILS_ROOT + '/config/security/',
65
+ RAILS_ROOT + '/config/', RAILS_ROOT + '/security/']
66
+
67
+ def self.get_file_name(f,ext)
68
+ SEARCH_PATH.each do |fname1|
69
+ [f, f+'.'+ext].each do |fname2|
70
+ return (fname1 + fname2) if File.exist?(fname1 + fname2)
71
+ end
72
+ end
73
+ raise "File not found: '#{f+'.'+ext}'"
74
+ end
75
+
76
+ def self.policy_factories
77
+ # Create a new factory if it is needed
78
+ @factories ||= Hash.new { |h,k| h[k] = AnnotationSecurity::PolicyFactory.new(k) }
79
+ end
80
+ end
@@ -0,0 +1,273 @@
1
+ #
2
+ # = lib/annotation_security/manager/relation_loader.rb
3
+ #
4
+
5
+ # Class responsible for loading the relation definitions for resources.
6
+ #
7
+ # == Defining a relation for a resource
8
+ #
9
+ # This example defines the owner relation between a picture and a user.
10
+ # A relation definition is a proc that returns true if the relation exists.
11
+ # All three examples are equivalent. However, in most cases the first way is
12
+ # the way you want to use.
13
+ # AnnotationSecurity.define_relations do
14
+ # resource :picture do
15
+ # owner { |user,picture| picture.user == user }
16
+ # end
17
+ # end
18
+ #
19
+ # If you need only one relation for a resource class, use this example:
20
+ # AnnotationSecurity.define_relations do
21
+ # picture.owner { |user,picture| picture.user == user }
22
+ # end
23
+ #
24
+ # If the entire file contains definitions for only one resource class,
25
+ # you might try this:
26
+ # AnnotationSecurity.define_relations :picture do
27
+ # owner { |user,picture| picture.user == user }
28
+ # end
29
+ #
30
+ # === Defining a relation for many resources
31
+ #
32
+ # Use +resources+ to define a relation once for more than one resource class.
33
+ # AnnotationSecurity.define_relations do
34
+ # resources(:picture, :comment) do
35
+ # owner { |user,res| res.user == user }
36
+ # end
37
+ # end
38
+ # As for one resource, you can also use
39
+ # AnnotationSecurity.define_relations do
40
+ # resources(:picture, :comment).owner { |user,res| res.user == user }
41
+ # end
42
+ # or
43
+ # AnnotationSecurity.define_relations(:picture, :comment) do
44
+ # owner { |user,res| res.user == user }
45
+ # end
46
+ #
47
+ # It is also possible to define relations for all resources:
48
+ # AnnotationSecurity.define_relations do
49
+ # all_resources do
50
+ # related { owner or friend_of_owner }
51
+ # end
52
+ # end
53
+ # or
54
+ # AnnotationSecurity.define_relations do
55
+ # all_resources.related { owner or friend_of_owner }
56
+ # end
57
+ #
58
+ # Notice that +owner+ and +friend_of_owner+ are relations that can be defined
59
+ # individually for each resource. The 2 parameters +user+ and +resource_object+
60
+ # dont need to be specified if they are not used.
61
+ #
62
+ # == Details on defining a relation
63
+ #
64
+ # As you have seen, the default way to define a relation is using a proc,
65
+ # like
66
+ # owner { |user,picture| picture.user == user }
67
+ # related { owner or friend_of_owner }
68
+ #
69
+ # If the condition is simple and uses only other relations,
70
+ # it also can be specified by a string:
71
+ # related 'if owner or friend_of_owner'
72
+ #
73
+ # === Implicit conditions
74
+ # Besides a string or a proc, a rule definition can contain a list of flags
75
+ # and an options-hash.
76
+ #
77
+ # ==== The :is option
78
+ #
79
+ # A Relation to which the <tt>:is => symbol</tt> option is passed as a parameter
80
+ # only exists if the relation exists and <tt>is_symbol?</tt> invoked on the
81
+ # current user evaluates to true.
82
+ #
83
+ # Let the user class have a method <tt>is_super_user?</tt>, which returns true
84
+ # or false, depending on wheter the user is a super user. This method can be
85
+ # used for defining a relation +super_owner+, that is true if the user is the
86
+ # owner and a super user.
87
+ #
88
+ # owner { |user,picture| picture.user == user }
89
+ # super_owner(:is => :super_user) "if owner"
90
+ #
91
+ # super_user(:system, :is => :super_user)
92
+ #
93
+ # ==== The :as option
94
+ #
95
+ # For a relation to which the <tt>:as => symbol</tt> option is passed as a
96
+ # parameter the current user is replaced by the invocation of
97
+ # <tt>current_credential.as_[symbol]</tt>. The method invocation may return +nil+
98
+ # indicating that the transformation failed. In this case the relation for
99
+ # which <tt>:as => ..</tt> was specified does not exist.
100
+ #
101
+ # ==== :require_credential
102
+ # By default, a relation requires a user to be executed. Therefore, rights will
103
+ # always fail if the user is nil. To enable rights like 'unless logged_in', the
104
+ # :require_credential option can be set to false.
105
+ # logged_in(:system, :require_credential => false) { |user| not user.nil? }
106
+ #
107
+ # === Evaluation time
108
+ # While most relations are between the user and a resource object, some are
109
+ # beween the user and an entire class of objects. This means that no instance of
110
+ # a resource is required to tell whether the user has that relation or not.
111
+ #
112
+ # ==== The :resource flag
113
+ # This flag is set by default. It is set for relations that need a resource.
114
+ #
115
+ # owner { |user,picture| picture.user == user }
116
+ # # is short for
117
+ # # owner(:resource) { |user,picture| picture.user == user }
118
+ #
119
+ # ==== The :system flag
120
+ # You can use the :system flag to denote that a relation does not
121
+ # require a resource object.
122
+ #
123
+ # all_resources do
124
+ # super_user(:system, :is => :super_user)
125
+ # end
126
+ #
127
+ # It is possible to define system relations only for certain resources, and they
128
+ # do not conflict with resource relations.
129
+ #
130
+ # resource :present do
131
+ # receiver(:system) { |user| user.was_good? }
132
+ # receiver { |user,present| present.receiver == user }
133
+ # end
134
+ #
135
+ # The advantage of system relations is that they improve the rights evaluation.
136
+ # Consider the right
137
+ # present:
138
+ # receive: if receiver
139
+ #
140
+ # If an action is invoked requiring the receive-present right,
141
+ # AnnotationSecurity will evaluate the system relation before even entering the
142
+ # action, thus improving the fail fast behavior and avoiding unnecessary
143
+ # operations.
144
+ #
145
+ # Once a present object is observed during the action, the resource relation
146
+ # will be evaluated as well.
147
+ #
148
+ # ==== The :pretest flag
149
+ #
150
+ # Using the :pretest flag, it is possible to define both resource and system
151
+ # relations in one block.
152
+ #
153
+ # resource :present do
154
+ # receiver(:pretest) do |user, present|
155
+ # if present
156
+ # present.receiver == user
157
+ # else
158
+ # user.was_good?
159
+ # end
160
+ # end
161
+ # end
162
+ #
163
+ # This can be helpfull if your relation depends on other relations, where a
164
+ # resource and a system version is available.
165
+ #
166
+ # all_resources do
167
+ # responsible(:pretest) { lecturer or corrector }
168
+ # lecturer(:system, :as => :lecturer)
169
+ # corrector(:system, :as => :corrector)
170
+ # end
171
+ #
172
+ # resource :course do
173
+ # lecturer(:as => :lecturer) { |lecturer, course| course.lecturers.include? lecturer }
174
+ # corrector(:as => :corrector) { |corrector, course| course.correctors.include? corrector }
175
+ # end
176
+ # # For other resources, lecturer and corrector are defined differently
177
+ #
178
+ # === Defining relations as strings
179
+ #
180
+ # Instead of a block, a string can be used to define the relation.
181
+ # responsible :pretest, "if lecturer or corrector"
182
+ #
183
+ # The string syntax provides more simplifications, like referring to relations
184
+ # of other resources.
185
+ #
186
+ # This example will evaluate the course-correction relation for the course
187
+ # property of an assignment resource.
188
+ # resource :assignment do
189
+ # corrector "if course.corrector: course"
190
+ # end
191
+ #
192
+ # As the course class includes AnnotationSecurity::Resource, the resource type
193
+ # is not explicitly needed.
194
+ # resource :assignment_result do
195
+ # corrector "if corrector: assignment.course"
196
+ # end
197
+ #
198
+ #
199
+ class AnnotationSecurity::RelationLoader
200
+
201
+ # Load relations of the +block+
202
+ # * +resources+ (optional) list of resources
203
+ # * +block+ block with relation definitions
204
+ def self.define_relations(*resources, &block)
205
+ if resources.blank?
206
+ class_eval(&block)
207
+ else
208
+ resources(*resources,&block)
209
+ end
210
+ end
211
+
212
+ #
213
+ def self.method_missing(symbol,*args,&block) #:nodoc:
214
+ return super unless args.empty?
215
+ resources(symbol,&block)
216
+ end
217
+
218
+ # Defines relations for a resource
219
+ # * +block+ (optional) proc with relation definitions
220
+ def self.resource(resource,&block)
221
+ resources(resource,&block)
222
+ end
223
+
224
+ # Defines relations for a list of resources
225
+ # * +block+ (optional) proc with relation definitions
226
+ def self.resources(*resources,&block)
227
+ new(resources,&block)
228
+ end
229
+
230
+ # Defines relations for all resources
231
+ # * +block+ (optional) proc with relation definitions
232
+ def self.all_resources(&block)
233
+ resources(:all_resources,&block)
234
+ end
235
+
236
+ ## ===========================================================================
237
+ ## Instance
238
+
239
+ # An instance of RelationLoader is responsible for loading the relations
240
+ # for a set of resources.
241
+ def initialize(resources,&block) #:nodoc:
242
+ @factories = get_factories_for(resources)
243
+ instance_eval(&block) if block
244
+ end
245
+
246
+ # if a method is missing this will be a new relation for the resource class
247
+ def method_missing(symbol,*args,&block) #:nodoc:
248
+ define_relation(symbol,*args,&block)
249
+ end
250
+
251
+ # Defines a new relation for the current resources. However, instead of using
252
+ # define_relation(:relation_name,args) { |user,res| some_condition }
253
+ # it is recommended to use
254
+ # relation_name(args) { |user,res| some_condition }
255
+ #
256
+ # ==== Parameters
257
+ # * +symbol+ name of the relation
258
+ # * +args+ additonal arguments, see AnnotationSecurity::Rule for details
259
+ # * +block+ (optional) The condition can be passed either as string or as proc
260
+ #
261
+ def define_relation(symbol,*args,&block)
262
+ @factories.each do |factory|
263
+ factory.add_rule(symbol,*args,&block)
264
+ end
265
+ end
266
+
267
+ private
268
+
269
+ def get_factories_for(resources)
270
+ resources.collect{ |res| AnnotationSecurity::PolicyManager.policy_factory(res) }
271
+ end
272
+
273
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # = lib/annotation_security/manager/resource_manager.rb
3
+ #
4
+
5
+ # Needed to find resource objects when only their id is known
6
+ #
7
+ class AnnotationSecurity::ResourceManager # :nodoc:
8
+
9
+ @classes = {}
10
+
11
+ def self.add_resource_class(res_type,klass)
12
+ @classes.delete_if { |k,v| v == klass }
13
+ @classes[res_type] = klass
14
+ end
15
+
16
+ def self.get_resource_class(res_type)
17
+ @classes[res_type]
18
+ end
19
+
20
+ unless RAILS_ENV == 'production'
21
+ def self.get_resource_class(res_type)
22
+ c = @classes[res_type]
23
+ unless c
24
+ res_type.to_s.camelize.constantize # load the class
25
+ c = @classes[res_type]
26
+ end
27
+ c
28
+ end
29
+ end
30
+
31
+ # Call get_resource of the class that is registered for +res_type+
32
+ def self.get_resource(res_type,object)
33
+ c = get_resource_class(res_type)
34
+ c ? c.get_resource(object) : object
35
+ end
36
+ end
@@ -0,0 +1,88 @@
1
+ #
2
+ # = lib/annotation_security/manager/right_loader.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::RightLoader
6
+ # Contains the right loader class, which is responsible for loading
7
+ # right definitions for resources. Load rights from a yaml file or a hash.
8
+ #
9
+ # == Example YAML
10
+ #
11
+ # The file <tt>config/security/rights.yml</tt> inside a rails app
12
+ # might look like this:
13
+ # picture:
14
+ # # a user may show a picture if he fulfils the 'related'-relation
15
+ # show: if related
16
+ # comment:
17
+ # # you have to be logged in to view comments
18
+ # show: if logged_in
19
+ # user:
20
+ # # like in ruby, 'unless' is equivalent to 'if not'
21
+ # register: unless logged_in
22
+ # delete: if administrator # comments are also possible behind a line
23
+ # user_content:
24
+ # # all rights of 'user_content' are defined for 'picture' and 'comment' too
25
+ # applies_to: picture, comment
26
+ # create: if logged_in
27
+ # edit: if owner
28
+ # delete: if owner or administrator
29
+ # The file can be loaded via <code>AnnotationSecurity#load_rights('rights')</code>.
30
+ #
31
+ # A right's condition can use the keywords +if+, +unless+, +and+, +or+ and
32
+ # +not+, brackets, other rights and all of the resource's relations
33
+ # (see AnnotationSecurity::RelationLoader). For better readability you may
34
+ # add the prefixes +may+, +is+, +can+ or +has+,
35
+ # or append one of the suffixes +for+, +in+, +of+ or +to+.
36
+ #
37
+ # user_content:
38
+ # edit: if is_owner_of
39
+ # delete: if may_edit or is_administrator
40
+ #
41
+ # However, it is recommended to use this feature sparingly.
42
+ #
43
+ class AnnotationSecurity::RightLoader
44
+
45
+ # Goes through all resources of +hash+ and load the defined rights.
46
+ #
47
+ def self.define_rights(hash) # :nodoc:
48
+ if hash
49
+ hash.each_pair do |resource_class, rights|
50
+ new(resource_class).define_rights(rights)
51
+ end
52
+ end
53
+ end
54
+
55
+ # An instance of RightLoader is responsible for loading the rights of a
56
+ # resource class.
57
+ #
58
+ def initialize(resource) # :nodoc:
59
+ @factory = AnnotationSecurity::PolicyManager.policy_factory(resource)
60
+ end
61
+
62
+ # Goes through all rights in +hash+ and creates rules for all policies these
63
+ # rights apply to.
64
+ #
65
+ def define_rights(hash) # :nodoc:
66
+ factories = extract_applies_to(hash) << @factory
67
+ hash.each_pair do |right,condition|
68
+ # Important: set the :right-flag to activate automatic detection of
69
+ # the other flags (static,dynamic,require_user)
70
+ factories.each { |f| f.add_rule(right,:right,condition) }
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # Looks for the key 'applies_to', which is no right but a command to apply
77
+ # all rights of the current resource class to the resource classes listed
78
+ # in the value.
79
+ #
80
+ def extract_applies_to(hash)
81
+ applies_to = hash.delete('applies_to') || hash.delete(:applies_to)
82
+ return [] if applies_to.blank?
83
+ applies_to = [applies_to] if applies_to.is_a? String
84
+ applies_to.collect{ |r| r.split(',') }.flatten.
85
+ collect{ |r| AnnotationSecurity::PolicyManager.policy_factory(r.strip) }
86
+ end
87
+
88
+ end
@@ -0,0 +1,61 @@
1
+ #
2
+ # = lib/annotation_security/model_observer.rb
3
+ #
4
+ # Contains SecurityObserver which implements constraint checking for model
5
+ # classes.
6
+ #
7
+
8
+ module AnnotationSecurity
9
+
10
+ # Observes changes in models and applies security policy to them
11
+ #
12
+ class ModelObserver < ::ActiveRecord::Observer # :nodoc:
13
+
14
+ # Sets the observed model classes
15
+ #
16
+ observe # will be set automatically. However, observe must not be removed
17
+
18
+ def before_validation_on_create(record)
19
+ SecurityContext.observe record
20
+ end
21
+
22
+ def before_validation_on_update(record)
23
+ SecurityContext.observe record
24
+ end
25
+
26
+ # after_find is removed in favour of after_initialize
27
+
28
+ def after_initialize(record)
29
+ if record.new_record?
30
+ # The record is new
31
+ else
32
+ # The record came out of database
33
+ SecurityContext.observe record
34
+ end
35
+ end
36
+
37
+ def before_destroy(record)
38
+ SecurityContext.observe record
39
+ end
40
+
41
+ # Re-register on classes you are observing
42
+ # See http://riotprojects.com/2009/1/18/active-record-observers-in-gems-plugins
43
+ #
44
+ def reload_model_observer
45
+ observed_classes.each do |klass|
46
+ add_observer!(klass.name.constantize)
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def add_observer!(klass)
53
+ klass.delete_observer(self)
54
+ super
55
+
56
+ if respond_to?(:after_initialize) && !klass.method_defined?(:after_initialize)
57
+ klass.class_eval 'def after_initialize() end'
58
+ end
59
+ end
60
+ end
61
+ end