declarative_authorization-dta 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 (45) hide show
  1. data/CHANGELOG +148 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +504 -0
  4. data/Rakefile +35 -0
  5. data/app/controllers/authorization_rules_controller.rb +259 -0
  6. data/app/controllers/authorization_usages_controller.rb +23 -0
  7. data/app/helpers/authorization_rules_helper.rb +218 -0
  8. data/app/views/authorization_rules/_change.erb +58 -0
  9. data/app/views/authorization_rules/_show_graph.erb +37 -0
  10. data/app/views/authorization_rules/_suggestions.erb +48 -0
  11. data/app/views/authorization_rules/change.html.erb +169 -0
  12. data/app/views/authorization_rules/graph.dot.erb +68 -0
  13. data/app/views/authorization_rules/graph.html.erb +40 -0
  14. data/app/views/authorization_rules/index.html.erb +17 -0
  15. data/app/views/authorization_usages/index.html.erb +36 -0
  16. data/authorization_rules.dist.rb +20 -0
  17. data/config/routes.rb +10 -0
  18. data/garlic_example.rb +20 -0
  19. data/init.rb +5 -0
  20. data/lib/declarative_authorization.rb +17 -0
  21. data/lib/declarative_authorization/authorization.rb +687 -0
  22. data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
  23. data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
  24. data/lib/declarative_authorization/development_support/change_supporter.rb +620 -0
  25. data/lib/declarative_authorization/development_support/development_support.rb +243 -0
  26. data/lib/declarative_authorization/helper.rb +60 -0
  27. data/lib/declarative_authorization/in_controller.rb +623 -0
  28. data/lib/declarative_authorization/in_model.new.rb +298 -0
  29. data/lib/declarative_authorization/in_model.rb +463 -0
  30. data/lib/declarative_authorization/maintenance.rb +212 -0
  31. data/lib/declarative_authorization/obligation_scope.rb +354 -0
  32. data/lib/declarative_authorization/rails_legacy.rb +22 -0
  33. data/lib/declarative_authorization/railsengine.rb +6 -0
  34. data/lib/declarative_authorization/reader.rb +521 -0
  35. data/lib/tasks/authorization_tasks.rake +82 -0
  36. data/test/authorization_test.rb +1065 -0
  37. data/test/controller_filter_resource_access_test.rb +511 -0
  38. data/test/controller_test.rb +465 -0
  39. data/test/dsl_reader_test.rb +178 -0
  40. data/test/helper_test.rb +172 -0
  41. data/test/maintenance_test.rb +46 -0
  42. data/test/model_test.rb +2216 -0
  43. data/test/schema.sql +62 -0
  44. data/test/test_helper.rb +152 -0
  45. metadata +108 -0
@@ -0,0 +1,298 @@
1
+ # Authorization::AuthorizationInModel
2
+ require File.dirname(__FILE__) + '/authorization.rb'
3
+ require File.dirname(__FILE__) + '/obligation_scope.rb'
4
+
5
+ module Authorization
6
+
7
+ module AuthorizationInModel
8
+
9
+ # If the user meets the given privilege, permitted_to? returns true
10
+ # and yields to the optional block.
11
+ def permitted_to? (privilege, options = {}, &block)
12
+ options = {
13
+ :user => Authorization.current_user,
14
+ :object => self
15
+ }.merge(options)
16
+ Authorization::Engine.instance.permit?(privilege,
17
+ {:user => options[:user],
18
+ :object => options[:object]},
19
+ &block)
20
+ end
21
+
22
+ # Works similar to the permitted_to? method, but doesn't accept a block
23
+ # and throws the authorization exceptions, just like Engine#permit!
24
+ def permitted_to! (privilege, options = {} )
25
+ options = {
26
+ :user => Authorization.current_user,
27
+ :object => self
28
+ }.merge(options)
29
+ Authorization::Engine.instance.permit!(privilege,
30
+ {:user => options[:user],
31
+ :object => options[:object]})
32
+ end
33
+
34
+ def self.included(base) # :nodoc:
35
+ #base.extend(ClassMethods)
36
+ base.module_eval do
37
+ scopes[:with_permissions_to] = lambda do |parent_scope, *args|
38
+ options = args.last.is_a?(Hash) ? args.pop : {}
39
+ privilege = (args[0] || :read).to_sym
40
+ privileges = [privilege]
41
+ context =
42
+ if options[:context]
43
+ options[:context]
44
+ elsif parent_scope.respond_to?(:proxy_reflection)
45
+ parent_scope.proxy_reflection.klass.name.tableize.to_sym
46
+ elsif parent_scope.respond_to?(:decl_auth_context)
47
+ parent_scope.decl_auth_context
48
+ else
49
+ parent_scope.name.tableize.to_sym
50
+ end
51
+
52
+ user = options[:user] || Authorization.current_user
53
+
54
+ engine = options[:engine] || Authorization::Engine.instance
55
+ engine.permit!(privileges, :user => user, :skip_attribute_test => true,
56
+ :context => context)
57
+
58
+ obligation_scope_for( privileges, :user => user,
59
+ :context => context, :engine => engine, :model => parent_scope)
60
+ end
61
+
62
+ # Builds and returns a scope with joins and conditions satisfying all obligations.
63
+ def self.obligation_scope_for( privileges, options = {} )
64
+ options = {
65
+ :user => Authorization.current_user,
66
+ :context => nil,
67
+ :model => self,
68
+ :engine => nil,
69
+ }.merge(options)
70
+ engine = options[:engine] || Authorization::Engine.instance
71
+
72
+ obligation_scope = ObligationScope.new( options[:model], {} )
73
+ engine.obligations( privileges, :user => options[:user], :context => options[:context] ).each do |obligation|
74
+ obligation_scope.parse!( obligation )
75
+ end
76
+
77
+ obligation_scope.scope
78
+ end
79
+
80
+ # Named scope for limiting query results according to the authorization
81
+ # of the current user. If no privilege is given, :+read+ is assumed.
82
+ #
83
+ # User.with_permissions_to
84
+ # User.with_permissions_to(:update)
85
+ # User.with_permissions_to(:update, :context => :users)
86
+ #
87
+ # As in the case of other named scopes, this one may be chained:
88
+ # User.with_permission_to.find(:all, :conditions...)
89
+ #
90
+ # Options
91
+ # [:+context+]
92
+ # Context for the privilege to be evaluated in; defaults to the
93
+ # model's table name.
94
+ # [:+user+]
95
+ # User to be used for gathering obligations; defaults to the
96
+ # current user.
97
+ #
98
+ def self.with_permissions_to (*args)
99
+ scopes[:with_permissions_to].call(self, *args)
100
+ end
101
+
102
+ # Activates model security for the current model. Then, CRUD operations
103
+ # are checked against the authorization of the current user. The
104
+ # privileges are :+create+, :+read+, :+update+ and :+delete+ in the
105
+ # context of the model. By default, :+read+ is not checked because of
106
+ # performance impacts, especially with large result sets.
107
+ #
108
+ # class User < ActiveRecord::Base
109
+ # using_access_control
110
+ # end
111
+ #
112
+ # If an operation is not permitted, a Authorization::AuthorizationError
113
+ # is raised.
114
+ #
115
+ # To activate model security on all models, call using_access_control
116
+ # on ActiveRecord::Base
117
+ # ActiveRecord::Base.using_access_control
118
+ #
119
+ # Available options
120
+ # [:+context+] Specify context different from the models table name.
121
+ # [:+include_read+] Also check for :+read+ privilege after find.
122
+ #
123
+ def self.using_access_control (options = {})
124
+ options = {
125
+ :context => nil,
126
+ :include_read => false
127
+ }.merge(options)
128
+
129
+ class_eval do
130
+ [:create, :update, [:destroy, :delete]].each do |action, privilege|
131
+ send(:"before_#{action}") do |object|
132
+ Authorization::Engine.instance.permit!(privilege || action,
133
+ :object => object, :context => options[:context])
134
+ end
135
+ end
136
+
137
+ if options[:include_read]
138
+ # after_find is only called if after_find is implemented
139
+ after_find do |object|
140
+ Authorization::Engine.instance.permit!(:read, :object => object,
141
+ :context => options[:context])
142
+ end
143
+ #Patch - Taking it down to attribute level
144
+
145
+ #Protect ActiveRecord-attribute
146
+ # By calling this method, the ActiveRecord-attribute "name" is protected
147
+ # * reading requires :read_name or :read
148
+ # * writing requires :write_name or :write
149
+ # This method is called for all ActiveRecord attributes by default - in contrast to def protect_attribute(name) this methods
150
+ # uses def read_attribute(:name) and write_attribute(:name,value) as fall-backs
151
+ def self.protect_ar_attribute(name)
152
+ #Alias old methods (if no alias is available), override methods
153
+ class_eval <<-EOL
154
+ alias_method :no_acl_#{name}, :#{name} if respond_to?(:#{name}) && !respond_to(:no_acl_#{name})
155
+ alias_method :no_acl_#{name}=, :#{name}= if respond_to?(:#{name}=) && !respond_to(:no_acl_#{name}=)
156
+ def #{name}()
157
+ permitted_to!(:read_#{name}) if !permitted_to?(:read)
158
+ if(respond_to?(:no_acl_#{name}))
159
+ return send(:no_acl_#{name})
160
+ else
161
+ return read_attribute(:no_acl_#{name})
162
+ end
163
+ end
164
+ def #{name}=(v)
165
+ permitted_to!(:read_#{write}) if !permitted_to?(:write)
166
+ if(respond_to?(:no_acl_#{name}=))
167
+ return send(:no_acl_#{name}=,v)
168
+ else
169
+ return write_attribute(:no_acl_#{name})
170
+ end
171
+ end
172
+ EOL
173
+ end
174
+
175
+ #Protect attribute
176
+ # By calling this method, the attribute "name" is protected
177
+ # * reading requires :read_name or :read
178
+ # * writing requires :write_name or :write
179
+ def protect_attribute(name)
180
+ #Protecting an attributes means protecting its setter and getters
181
+ protect_method(name,:read)
182
+ protect_method("#{name}=",:write)
183
+ end
184
+
185
+
186
+ #Protect method from beeing called without permission
187
+ # * name: Name of method
188
+ # * mode: :read or :write (or anything else in obscure scenarios)
189
+ def protect_instance_method(name,mode)
190
+ #Alias old method (if no alias is available), override method
191
+ class_eval <<-EOL
192
+ alias_method :no_acl_#{name}, :#{name} if !respond_to(:no_acl_#{name})
193
+ EOL
194
+ instance_eval <<-EOL
195
+ def #{name}(*args,&block)
196
+ permitted_to!(:#{mode}_#{name}) if !permitted_to?(:#{mode})
197
+ return send(:#{name},*args,&block)
198
+ end
199
+ EOL
200
+ end
201
+
202
+
203
+
204
+ if(options[:include_attributes]) #If attribute / getter-setter-access ought to be checekd#
205
+ #Try to parse input - if there's any
206
+ #Provide defaults
207
+ require_read_for = []
208
+ require_write_for = []
209
+ whitelist = []
210
+ #try reading parameters
211
+ begin
212
+ require_read_for = options[:include_attributes][0][:require_read_for] || []
213
+ require_write_for = options[:include_attributes][0][:require_write_for] || []
214
+ whitelist = options[:include_attributes][0][:whitelist] || []
215
+ rescue;end
216
+
217
+ #convert arrays to sets
218
+ require_read_for = require_read_for.to_set
219
+ require_write_for = require_write_for.to_set
220
+ whitelist = whitelist.to_set
221
+
222
+ #Enable callback for instance-level meta programming
223
+ def after_initialize; end
224
+
225
+
226
+ #1 Generate guards for ar-attributes
227
+ column_names.each do |name|
228
+ protect_ar_attribute(name) unless name.to_s == self.primary_key.to_s || whitelist.include?(name)
229
+ end
230
+
231
+ #2 Evaluate :require_read_for, :require_write_for
232
+ if protect_attributes
233
+ after_initialize do |object|
234
+ require_write_for.each {|attr| object.protect_instance_method(attr,:write) }
235
+ require_read_for.each {|attr| object.protect_instance_method(attr,:read) }
236
+ end
237
+ end
238
+
239
+ #3 Generate guards for ar-proxies
240
+ after_initialize do |object|
241
+ end
242
+
243
+ #2nd Generate guards for ar-proxies
244
+ after_initialize do |object|
245
+ reflect_on_all_associations.each do |assoc|
246
+ #Respect excludes
247
+ #Ok, we've to intercept these calls (See: ActiveRecord::Associations::ClassMethods)
248
+ # one-to-one: other_id, other_id=(id), other, other=(other), build_other(attributes={}), create_other(attributes={})
249
+ # one-to-many / many-to-many: others, others=(other,other,...), other_ids, other_ids=(id,id,...), others<<
250
+ object.inject_acl_object_getter_setter(assoc.name.to_s) unless whitelist.include?(assoc.name)
251
+
252
+ if(assoc.collection?) #Collection? if so, many-to-many case
253
+ object.protect_instance_method("#{assoc.name.to_s.singularize}_ids",:read) unless whitelist.include?("#{assoc.name.to_s.singularize}_ids".to_sym)
254
+ object.protect_instance_method("#{assoc.name.to_s.singularize}_ids",:write) unless whitelist.include?("#{assoc.name.to_s.singularize}_ids".to_sym)
255
+ #inject_acl_write_check("#{assoc.name}<<")
256
+ else
257
+ object.protect_instance_method("#{assoc.name}_id",:read) unless assoc.macro != :has_one || whitelist.include?("#{assoc.name}_id".to_sym)
258
+ object.protect_instance_method("#{assoc.name}_id",:write) unless assoc.macro != :has_one || whitelist.include?("#{assoc.name}_id".to_sym)
259
+ object.protect_instance_method("build_#{assoc.name}",:write) unless whitelist.include?("build_#{assoc.name}".to_sym)
260
+ object.protect_instance_method("create_#{assoc.name}",:read) unless whitelist.include?("create_#{assoc.name}".to_sym)
261
+ end
262
+ end
263
+ end
264
+
265
+ def readable_attributes #Define Attribute - Arrays as a replacement for model.attributes
266
+ return attributes if permitted_to?(:read)
267
+ attributes.reject do |k,v|
268
+ !permitted_to?("read_#{k}".to_sym) && k.to_s != self.class.primary_key.to_s
269
+ end
270
+ end
271
+ def writable_attributes
272
+ return attributes if permitted_to?(:write)
273
+ attributes.reject do |k,v|
274
+ !permitted_to?("write_#{k}#".to_sym) && k.to_s != self.class.primary_key.to_s
275
+ end
276
+ end
277
+ end
278
+ # #End patch - Taking it down to attribute level
279
+
280
+ if Rails.version < "3"
281
+ def after_find; end
282
+ end
283
+ end
284
+
285
+ def self.using_access_control?
286
+ true
287
+ end
288
+ end
289
+ end
290
+
291
+ # Returns true if the model is using model security.
292
+ def self.using_access_control?
293
+ false
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,463 @@
1
+ # Authorization::AuthorizationInModel
2
+ require File.dirname(__FILE__) + '/authorization.rb'
3
+ require File.dirname(__FILE__) + '/obligation_scope.rb'
4
+
5
+ module Authorization
6
+
7
+ module AuthorizationInModel
8
+
9
+ # If the user meets the given privilege, permitted_to? returns true
10
+ # and yields to the optional block.
11
+ def permitted_to? (privilege, options = {}, &block)
12
+ options = {
13
+ :user => Authorization.current_user,
14
+ :object => self
15
+ }.merge(options)
16
+ Authorization::Engine.instance.permit?(privilege,
17
+ {:user => options[:user],
18
+ :object => options[:object]},
19
+ &block)
20
+ end
21
+
22
+ # Works similar to the permitted_to? method, but doesn't accept a block
23
+ # and throws the authorization exceptions, just like Engine#permit!
24
+ def permitted_to! (privilege, options = {} )
25
+ options = {
26
+ :user => Authorization.current_user,
27
+ :object => self
28
+ }.merge(options)
29
+ Authorization::Engine.instance.permit!(privilege,
30
+ {:user => options[:user],
31
+ :object => options[:object]})
32
+ end
33
+
34
+ #
35
+ # Returns true or false, depending on whether we can read/write a column based on all our rules
36
+ #
37
+ # PARAMS
38
+ #
39
+ # mode Symbol. :read/:write
40
+ # attribute String. the column we want to check
41
+ # application_defaults Boolean, whether we want to incude the application defaults or not
42
+ #
43
+ # RETURNS
44
+ #
45
+ # boolean, true/false
46
+ #
47
+ def allowed?(mode, attribute, exclude_application_defaults = false)
48
+ # Return false if mode is not read or write
49
+ return false unless [:read, :write].include?(mode)
50
+
51
+ # Variables needed to make checks
52
+ access_all_columns_sym = (mode == :read) ? self.class.read_all_privilege.to_sym : self.class.write_all_privilege.to_sym
53
+ whitelist_sym = (mode == :read) ? attribute.to_sym : (attribute + '=').to_sym
54
+ acl_sym = (mode == :read) ? ('read_' + attribute).to_sym : ('write_' + attribute).to_sym
55
+
56
+ # Perform checks, returns early on success
57
+ return true if attribute.to_s == self.class.primary_key.to_s # Always return true on primary key
58
+ return true if !exclude_application_defaults && get_application_default_attributes.include?(attribute.to_sym) # Test application defaults first
59
+ return true if permitted_to_without_include_attributes?(access_all_columns_sym) # Are we allowed read/write all?
60
+ return true if get_white_list.include?(whitelist_sym) # White Listed
61
+ return true if permitted_to_without_include_attributes?(acl_sym) # read/write_{attribute} given explicitly
62
+ false # Not allowed, return false
63
+ end
64
+
65
+ def self.included(base) # :nodoc:
66
+ #base.extend(ClassMethods)
67
+ base.module_eval do
68
+ scopes[:with_permissions_to] = lambda do |parent_scope, *args|
69
+ options = args.last.is_a?(Hash) ? args.pop : {}
70
+ privilege = (args[0] || :read).to_sym
71
+ privileges = [privilege]
72
+ context =
73
+ if options[:context]
74
+ options[:context]
75
+ elsif parent_scope.respond_to?(:proxy_reflection)
76
+ parent_scope.proxy_reflection.klass.name.tableize.to_sym
77
+ elsif parent_scope.respond_to?(:decl_auth_context)
78
+ parent_scope.decl_auth_context
79
+ else
80
+ parent_scope.name.tableize.to_sym
81
+ end
82
+
83
+ user = options[:user] || Authorization.current_user
84
+
85
+ engine = options[:engine] || Authorization::Engine.instance
86
+ engine.permit!(privileges, :user => user, :skip_attribute_test => true,
87
+ :context => context)
88
+
89
+ obligation_scope_for( privileges, :user => user,
90
+ :context => context, :engine => engine, :model => parent_scope)
91
+ end
92
+
93
+ # Builds and returns a scope with joins and conditions satisfying all obligations.
94
+ def self.obligation_scope_for( privileges, options = {} )
95
+ options = {
96
+ :user => Authorization.current_user,
97
+ :context => nil,
98
+ :model => self,
99
+ :engine => nil,
100
+ }.merge(options)
101
+ engine = options[:engine] || Authorization::Engine.instance
102
+
103
+ obligation_scope = ObligationScope.new( options[:model], {} )
104
+ engine.obligations( privileges, :user => options[:user], :context => options[:context] ).each do |obligation|
105
+ obligation_scope.parse!( obligation )
106
+ end
107
+
108
+ obligation_scope.scope
109
+ end
110
+
111
+ # Named scope for limiting query results according to the authorization
112
+ # of the current user. If no privilege is given, :+read+ is assumed.
113
+ #
114
+ # User.with_permissions_to
115
+ # User.with_permissions_to(:update)
116
+ # User.with_permissions_to(:update, :context => :users)
117
+ #
118
+ # As in the case of other named scopes, this one may be chained:
119
+ # User.with_permission_to.find(:all, :conditions...)
120
+ #
121
+ # Options
122
+ # [:+context+]
123
+ # Context for the privilege to be evaluated in; defaults to the
124
+ # model's table name.
125
+ # [:+user+]
126
+ # User to be used for gathering obligations; defaults to the
127
+ # current user.
128
+ #
129
+ def self.with_permissions_to (*args)
130
+ scopes[:with_permissions_to].call(self, *args)
131
+ end
132
+
133
+ # Activates model security for the current model. Then, CRUD operations
134
+ # are checked against the authorization of the current user. The
135
+ # privileges are :+create+, :+read+, :+update+ and :+delete+ in the
136
+ # context of the model. By default, :+read+ is not checked because of
137
+ # performance impacts, especially with large result sets.
138
+ #
139
+ # class User < ActiveRecord::Base
140
+ # using_access_control
141
+ # end
142
+ #
143
+ # If an operation is not permitted, a Authorization::AuthorizationError
144
+ # is raised.
145
+ #
146
+ # To activate model security on all models, call using_access_control
147
+ # on ActiveRecord::Base
148
+ # ActiveRecord::Base.using_access_control
149
+ #
150
+ # Available options
151
+ # [:+context+] Specify context different from the models table name.
152
+ # [:+include_read+] Also check for :+read+ privilege after find.
153
+ #
154
+ def self.using_access_control (options = {})
155
+ options = {
156
+ :context => nil,
157
+ :include_read => false
158
+ }.merge(options)
159
+
160
+ class_eval do
161
+ if options[:include_read]
162
+ # If we are limiting access by options[:include_attributes], then we do not want to do the check on the entire object
163
+ # instead we will allow the individual checks to determine what passes and what failes
164
+ unless(options[:include_attributes])
165
+ # after_find is only called if after_find is implemented
166
+ after_find do |object|
167
+ Authorization::Engine.instance.permit!(:read, :object => object,
168
+ :context => options[:context])
169
+ end
170
+
171
+ if Rails.version < "3"
172
+ def after_find; end
173
+ end
174
+ end
175
+ end
176
+
177
+ # If we are limiting access by options[:include_attributes], then we do not want to do the check on the entire object
178
+ # instead we will allow the individual checks to determine what passes and what failes
179
+ unless(options[:include_attributes])
180
+ [:create, :update, [:destroy, :delete]].each do |action, privilege|
181
+ send(:"before_#{action}") do |object|
182
+ Authorization::Engine.instance.permit!(privilege || action, :object => object, :context => options[:context])
183
+ end
184
+ end
185
+ end
186
+
187
+ #Inject an acl_write check for a given methid into method-chain
188
+ def self.inject_acl_write_check(method_name)
189
+ inject_acl_check(method_name,:write)
190
+ end
191
+
192
+ #Inject an acl_read check for a given methid into method-chain
193
+ def self.inject_acl_read_check(method_name)
194
+ inject_acl_check(method_name,:read)
195
+ end
196
+
197
+ #routine for helper methods
198
+ def self.inject_acl_check(method_name,mode)
199
+ command = <<-EOV
200
+ alias_method :no_acl_#{method_name}, :#{method_name} unless respond_to?(:no_acl_#{method_name})
201
+ def #{method_name}(*args,&block)
202
+ permitted_to!(:#{mode}_#{method_name}) if !permitted_to?(:#{mode})
203
+ return no_acl_#{method_name}(args#{',block' unless method_name.to_s.match(/=$/)}) unless args.blank? || block.blank?
204
+ return no_acl_#{method_name}(#{'block' unless method_name.to_s.match(/=$/)}) if args.blank? && block
205
+ return no_acl_#{method_name}(args) if !args.blank? && block.blank?
206
+ return no_acl_#{method_name}()
207
+ end
208
+ EOV
209
+ class_eval command
210
+ end
211
+
212
+ #Protecting an instance (used for generated code, ie ActiveRecord)
213
+ def inject_acl_object_check(method_name,mode)
214
+ command = <<-EOV
215
+ alias_method :no_acl_#{method_name}, :#{method_name} unless respond_to?(:no_acl_#{method_name})
216
+ def #{method_name}(*args,&block)
217
+ permitted_to!(:#{mode}_#{method_name}) if (!permitted_to?(:#{mode}))
218
+ return no_acl_#{method_name}(args#{',block' unless method_name.to_s.match(/=$/)}) unless args.blank? || block.blank?
219
+ return no_acl_#{method_name}(#{'block' unless method_name.to_s.match(/=$/)}) if args.blank? && block
220
+ return no_acl_#{method_name}(args) if !args.blank? && block.blank?
221
+ return no_acl_#{method_name}()
222
+ end
223
+ EOV
224
+ class_eval command
225
+ end
226
+
227
+ #Inject acl-aware setter / getter methods into method-chain
228
+ def inject_acl_object_getter_setter(method_name)
229
+ class_eval <<-EOV
230
+ alias_method :no_acl_#{method_name}, :#{method_name} unless respond_to? :no_acl_#{method_name}
231
+ alias_method :no_acl_#{method_name}=, :#{method_name}= unless respond_to? :no_acl_#{method_name}=
232
+ EOV
233
+ logger.info "Injecting #{method_name} to #{self}"
234
+ instance_eval <<-EOV
235
+
236
+ def #{method_name}
237
+ permitted_to!(:read_#{method_name}) unless permitted_to?(:#{read_all_privilege})
238
+ return no_acl_#{method_name}
239
+ end
240
+ def #{method_name}=(value)
241
+ permitted_to!(:write_#{method_name}) unless permitted_to?(:#{write_all_privilege})
242
+ return no_acl_#{method_name}=(value)
243
+ end
244
+ EOV
245
+ end
246
+
247
+ if(options[:include_attributes]) #If attribute / getter-setter-access ought to be checekd#
248
+ #parse attribute hash - sane input?
249
+ raise "Illegal syntax - :include_attributes must point to an array" unless options[:include_attributes][0].is_a?(Hash)
250
+
251
+ protect_ar = options[:include_attributes][0][:protect_ar] || []
252
+ raise "Illegal syntax :protect_ar must point to an array" unless protect_ar.blank? || protect_ar.is_a?(Array)
253
+ protect_ar = protect_ar.to_set
254
+
255
+ protect_read = options[:include_attributes][0][:protect_read]
256
+ raise "Illegal syntax :protect_read must point to an array" unless protect_read.nil? || protect_read.is_a?(Array)
257
+
258
+ protect_write = options[:include_attributes][0][:protect_write]
259
+ raise "Illegal syntax :protect_write must point to an array" unless protect_write.nil? || protect_write.is_a?(Array)
260
+
261
+ protect_attributes = options[:include_attributes][0][:protect_attributes]
262
+ raise "Illegal syntax :protect_attributes point to an array" unless protect_attributes.nil? || protect_attributes.is_a?(Array)
263
+
264
+ whitelist = options[:include_attributes][0][:whitelist] || []
265
+ raise "Illegal syntax :whitelist must point to an array" unless whitelist.blank? || whitelist.is_a?(Array)
266
+ whitelist = whitelist.to_set
267
+
268
+ application_default_attributes = options[:include_attributes][0][:application_default_attributes] || []
269
+ raise "Illegal syntax :application_default_attributes must point to an array" unless application_default_attributes.blank? || application_default_attributes.is_a?(Array)
270
+ application_default_attributes = application_default_attributes.to_set
271
+
272
+ #Enable callback for instance-level meta programming
273
+ def after_initialize; end
274
+
275
+ # Create helper methods, that can be called from within our code to access
276
+ # variables that are set up during initilization
277
+ instance_eval <<-EOV
278
+ #
279
+ # Determine what privilege to use for read all
280
+ #
281
+ def read_all_privilege
282
+ '#{options[:include_attributes][0][:read_all_privilege].blank? ? 'read' : options[:include_attributes][0][:read_all_privilege]}'
283
+ end
284
+
285
+ #
286
+ # Determine what privilege to use for write all
287
+ #
288
+ def write_all_privilege
289
+ '#{options[:include_attributes][0][:write_all_privilege].blank? ? 'write' : options[:include_attributes][0][:write_all_privilege]}'
290
+ end
291
+ EOV
292
+
293
+ class_eval <<-EOV
294
+ #
295
+ # Method to return the white list
296
+ #
297
+ def get_white_list
298
+ [#{whitelist.to_a.collect{|c| ":#{c}"}.join(',')}]
299
+ end
300
+
301
+ #
302
+ # Method to return the application_default_attributes
303
+ #
304
+ def get_application_default_attributes
305
+ [#{application_default_attributes.to_a.collect{|c| ":#{c}"}.join(',')}]
306
+ end
307
+ EOV
308
+
309
+ #1a Generate guards for ar-attributes
310
+ if protect_ar.include?(:attributes)
311
+ column_names.each do |name|
312
+ class_eval "begin; alias_method :no_acl_#{name}, :#{name};rescue;end #Alias-Methods - put acl stuff into method-chain
313
+ begin; alias_method :no_acl_#{name}=, :#{name}=; rescue; end
314
+ def #{name} #Define getters / setter with ACL-Checks
315
+ permitted_to!(:read_#{name}) if !permitted_to?(:#{read_all_privilege});
316
+ if(respond_to? 'no_acl_#{name}')
317
+ return no_acl_#{name}
318
+ else
319
+ return read_attribute(:#{name})
320
+ end
321
+ end" unless name.to_s == self.primary_key.to_s || whitelist.include?(name.to_sym) || application_default_attributes.include?(name.to_sym) || !options[:include_read] # Do not do reads, unless told so
322
+ class_eval %{def #{name}=(n)
323
+ permitted_to!(:write_#{name}) if !permitted_to?(:#{write_all_privilege});
324
+ if(respond_to? 'no_acl_#{name}=')
325
+ return no_acl_#{name}=(n)
326
+ else
327
+ return write_attribute(:#{name},n)
328
+ end
329
+ end} unless name.to_s == self.primary_key.to_s || whitelist.include?("#{name}=".to_sym) || application_default_attributes.include?(name.to_sym)
330
+ end
331
+ end
332
+
333
+ #1b Generate guards for non-ar attributes
334
+ if protect_attributes
335
+ after_initialize do |object|
336
+ protect_attributes.each { |attr| object.inject_acl_object_getter_setter(attr) }
337
+ end
338
+ end
339
+
340
+
341
+ #2nd Generate guards for ar-proxies
342
+ if protect_ar.include?(:proxies)
343
+ after_initialize do |object|
344
+ reflect_on_all_associations.each do |assoc|
345
+ #Respect excludes
346
+ #Ok, we've to intercept these calls (See: ActiveRecord::Associations::ClassMethods)
347
+ # one-to-one: other_id, other_id=(id), other, other=(other), build_other(attributes={}), create_other(attributes={})
348
+ # one-to-many / many-to-many: others, others=(other,other,...), other_ids, other_ids=(id,id,...), others<<
349
+ object.inject_acl_object_getter_setter(assoc.name.to_s) unless whitelist.include?(assoc.name)
350
+
351
+ if(assoc.collection?) #Collection? if so, many-to-many case
352
+ object.inject_acl_object_getter_setter("#{assoc.name.to_s.singularize}_ids") unless whitelist.include?("#{assoc.name.to_s.singularize}_ids".to_sym)
353
+ #inject_acl_write_check("#{assoc.name}<<")
354
+ else
355
+ object.inject_acl_object_getter_setter("#{assoc.name}_id") unless assoc.macro != :has_one || whitelist.include?("#{assoc.name}_id".to_sym)
356
+ object.inject_acl_object_check("build_#{assoc.name}",:write) unless whitelist.include?("build_#{assoc.name}".to_sym)
357
+ object.inject_acl_object_check("create_#{assoc.name}",:read) unless whitelist.include?("create_#{assoc.name}".to_sym)
358
+ end
359
+ end
360
+ end
361
+ end
362
+
363
+ #3rd - generate guards for specified methods
364
+ #3a - read-permission required
365
+ if(protect_read)
366
+ after_initialize do |object|
367
+ protect_read.each { |method| object.inject_acl_object_check(method,:read) }
368
+ end
369
+ end
370
+
371
+ #3b - write permission required
372
+ if(protect_write)
373
+ after_initialize do |object|
374
+ protect_write.each { |method| object.inject_acl_object_check(method,:write) }
375
+ end
376
+ end
377
+
378
+ #
379
+ # Returns a hash of key, value paris that are readable
380
+ #
381
+ def readable_attributes
382
+ return attributes if permitted_to?(self.class.read_all_privilege.to_sym)
383
+ attributes.reject do |k,v|
384
+ !allowed?(:read, k)
385
+ end
386
+ end
387
+
388
+ #
389
+ # Returns a hash of key, value paris that are showable, excluding application_default_attributes
390
+ #
391
+ def showable_attributes
392
+ return attributes if permitted_to?(self.class.read_all_privilege.to_sym)
393
+ attributes.reject do |k,v|
394
+ !allowed?(:read, k, true)
395
+ end
396
+ end
397
+
398
+ #
399
+ # Returns a hash of key, value paris that are writable
400
+ #
401
+ def writable_attributes
402
+ return attributes if permitted_to?(self.class.write_all_privilege.to_sym)
403
+ attributes.reject do |k,v|
404
+ !allowed?(:write, k)
405
+ end
406
+ end
407
+
408
+ #
409
+ # Returns a list of columns that are readable
410
+ #
411
+ def readable_columns
412
+ readable_attributes.keys
413
+ end
414
+
415
+ #
416
+ # Returns a list of columns that are writable
417
+ #
418
+ def writable_columns
419
+ writable_attributes.keys
420
+ end
421
+
422
+ #
423
+ # Returns a list of columns that are showable to the user
424
+ #
425
+ def showable_columns
426
+ showable_attributes.keys
427
+ end
428
+
429
+ #
430
+ # When calling permitted_to? on the model, we return true if whitelist or read/write all
431
+ # excluding application_default_attributes
432
+ #
433
+ def permitted_to_with_include_attributes?(privilege, options = {}, &block)
434
+ # Figure out what priv/attribute was passed, if it begins with read_ or write_
435
+ if reg = privilege.to_s.match(/(^write_|^read_)(.+)/)
436
+ mode, attribute = reg[1].chop.to_sym, reg[2] # Split the regular expression accordingly
437
+ if allowed?(mode, attribute, true) # Exclude application_default_attributes
438
+ yield if block_given?
439
+ return true
440
+ end
441
+ end
442
+
443
+ # Default back to old call
444
+ permitted_to_without_include_attributes?(privilege, options, &block)
445
+ end
446
+
447
+ alias_method_chain :permitted_to?, :include_attributes
448
+ end
449
+
450
+ def self.using_access_control?
451
+ true
452
+ end
453
+ end
454
+ end
455
+
456
+ # Returns true if the model is using model security.
457
+ def self.using_access_control?
458
+ false
459
+ end
460
+ end
461
+ end
462
+ end
463
+ end