uhees-declarative_authorization 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +77 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +490 -0
  4. data/Rakefile +43 -0
  5. data/app/controllers/authorization_rules_controller.rb +235 -0
  6. data/app/controllers/authorization_usages_controller.rb +23 -0
  7. data/app/helpers/authorization_rules_helper.rb +183 -0
  8. data/app/views/authorization_rules/_change.erb +49 -0
  9. data/app/views/authorization_rules/_show_graph.erb +37 -0
  10. data/app/views/authorization_rules/_suggestion.erb +9 -0
  11. data/app/views/authorization_rules/_suggestions.erb +24 -0
  12. data/app/views/authorization_rules/change.html.erb +124 -0
  13. data/app/views/authorization_rules/graph.dot.erb +68 -0
  14. data/app/views/authorization_rules/graph.html.erb +40 -0
  15. data/app/views/authorization_rules/index.html.erb +17 -0
  16. data/app/views/authorization_usages/index.html.erb +36 -0
  17. data/authorization_rules.dist.rb +20 -0
  18. data/config/routes.rb +7 -0
  19. data/garlic_example.rb +20 -0
  20. data/init.rb +5 -0
  21. data/lib/declarative_authorization.rb +15 -0
  22. data/lib/declarative_authorization/authorization.rb +630 -0
  23. data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
  24. data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
  25. data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
  26. data/lib/declarative_authorization/development_support/development_support.rb +243 -0
  27. data/lib/declarative_authorization/helper.rb +60 -0
  28. data/lib/declarative_authorization/in_controller.rb +367 -0
  29. data/lib/declarative_authorization/in_model.rb +150 -0
  30. data/lib/declarative_authorization/maintenance.rb +188 -0
  31. data/lib/declarative_authorization/obligation_scope.rb +297 -0
  32. data/lib/declarative_authorization/rails_legacy.rb +14 -0
  33. data/lib/declarative_authorization/reader.rb +438 -0
  34. data/test/authorization_test.rb +823 -0
  35. data/test/controller_test.rb +418 -0
  36. data/test/dsl_reader_test.rb +157 -0
  37. data/test/helper_test.rb +154 -0
  38. data/test/maintenance_test.rb +41 -0
  39. data/test/model_test.rb +1171 -0
  40. data/test/schema.sql +53 -0
  41. data/test/test_helper.rb +103 -0
  42. metadata +104 -0
@@ -0,0 +1,243 @@
1
+
2
+ module Authorization
3
+ module DevelopmentSupport
4
+ class AbstractAnalyzer
5
+ attr_reader :engine
6
+
7
+ def initialize (engine)
8
+ @engine = engine
9
+ end
10
+
11
+ def roles
12
+ AnalyzerEngine.roles(engine)
13
+ end
14
+
15
+ def rules
16
+ roles.collect {|role| role.rules }.flatten
17
+ end
18
+ end
19
+
20
+ # Groups utility methods and classes to better work with authorization object
21
+ # model.
22
+ module AnalyzerEngine
23
+
24
+ def self.roles (engine)
25
+ Role.all(engine)
26
+ end
27
+
28
+ def self.relevant_roles (engine, users)
29
+ users.collect {|user| user.role_symbols.map {|role_sym| Role.for_sym(role_sym, engine)}}.
30
+ flatten.uniq.collect {|role| [role] + role.ancestors}.flatten.uniq
31
+ end
32
+
33
+ def self.rule_for_permission (engine, privilege, context, role)
34
+ AnalyzerEngine.roles(engine).
35
+ find {|cloned_role| cloned_role.to_sym == role.to_sym}.rules.find do |rule|
36
+ rule.contexts.include?(context) and rule.privileges.include?(privilege)
37
+ end
38
+ end
39
+
40
+ def self.apply_change (engine, change)
41
+ case change[0]
42
+ when :add_role
43
+ role_symbol = change[1]
44
+ if engine.roles.include?(role_symbol)
45
+ false
46
+ else
47
+ engine.roles << role_symbol
48
+ true
49
+ end
50
+ when :add_privilege
51
+ privilege, context, role = change[1,3]
52
+ role = Role.for_sym(role.to_sym, engine)
53
+ privilege = Privilege.for_sym(privilege.to_sym, engine)
54
+ if ([privilege] + privilege.ancestors).any? {|ancestor_privilege| ([role] + role.ancestors).any? {|ancestor_role| !ancestor_role.rules_for_permission(ancestor_privilege, context).empty?}}
55
+ false
56
+ else
57
+ engine.auth_rules << AuthorizationRule.new(role.to_sym,
58
+ [privilege.to_sym], [context])
59
+ true
60
+ end
61
+ when :remove_privilege
62
+ privilege, context, role = change[1,3]
63
+ role = Role.for_sym(role.to_sym, engine)
64
+ privilege = Privilege.for_sym(privilege.to_sym, engine)
65
+ rules_with_priv = role.rules_for_permission(privilege, context)
66
+ if rules_with_priv.empty?
67
+ false
68
+ else
69
+ rules_with_priv.each do |rule|
70
+ rule.rule.privileges.delete(privilege.to_sym)
71
+ engine.auth_rules.delete(rule.rule) if rule.rule.privileges.empty?
72
+ end
73
+ true
74
+ end
75
+ end
76
+ end
77
+
78
+ class Role
79
+ @@role_objects = {}
80
+ attr_reader :role
81
+ def initialize (role, rules, engine)
82
+ @role = role
83
+ @rules = rules
84
+ @engine = engine
85
+ end
86
+
87
+ def source_line
88
+ @rules.empty? ? nil : @rules.first.source_line
89
+ end
90
+ def source_file
91
+ @rules.empty? ? nil : @rules.first.source_file
92
+ end
93
+
94
+ # ancestors' privileges are included in in the current role
95
+ def ancestors (role_symbol = nil)
96
+ role_symbol ||= @role
97
+ (@engine.role_hierarchy[role_symbol] || []).
98
+ collect {|lower_role| ancestors(lower_role) }.flatten +
99
+ (role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
100
+ end
101
+ def descendants (role_symbol = nil)
102
+ role_symbol ||= @role
103
+ (@engine.rev_role_hierarchy[role_symbol] || []).
104
+ collect {|higher_role| descendants(higher_role) }.flatten +
105
+ (role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
106
+ end
107
+
108
+ def rules
109
+ @rules ||= @engine.auth_rules.select {|rule| rule.role == @role}.
110
+ collect {|rule| Rule.new(rule, @engine)}
111
+ end
112
+ def rules_for_permission (privilege, context)
113
+ rules.select do |rule|
114
+ rule.matches?([@role], [privilege.to_sym], context)
115
+ end
116
+ end
117
+
118
+ def to_sym
119
+ @role
120
+ end
121
+ def self.for_sym (role_sym, engine)
122
+ @@role_objects[[role_sym, engine]] ||= new(role_sym, nil, engine)
123
+ end
124
+
125
+ def self.all (engine)
126
+ rules_by_role = engine.auth_rules.inject({}) do |memo, rule|
127
+ memo[rule.role] ||= []
128
+ memo[rule.role] << rule
129
+ memo
130
+ end
131
+ engine.roles.collect do |role|
132
+ new(role, (rules_by_role[role] || []).
133
+ collect {|rule| Rule.new(rule, engine)}, engine)
134
+ end
135
+ end
136
+ def self.all_for_privilege (privilege, context, engine)
137
+ privilege = privilege.is_a?(Symbol) ? Privilege.for_sym(privilege, engine) : privilege
138
+ privilege_symbols = ([privilege] + privilege.ancestors).map(&:to_sym)
139
+ all(engine).select {|role| role.rules.any? {|rule| rule.matches?([role.to_sym], privilege_symbols, context)}}.
140
+ collect {|role| [role] + role.descendants}.flatten.uniq
141
+ end
142
+ end
143
+
144
+ class Rule
145
+ @@rule_objects = {}
146
+ delegate :source_line, :source_file, :contexts, :matches?, :to => :@rule
147
+ attr_reader :rule
148
+ def initialize (rule, engine)
149
+ @rule = rule
150
+ @engine = engine
151
+ end
152
+ def privileges
153
+ PrivilegesSet.new(self, @engine, @rule.privileges.collect {|privilege| Privilege.for_sym(privilege, @engine) })
154
+ end
155
+ def self.for_rule (rule, engine)
156
+ @@rule_objects[[rule, engine]] ||= new(rule, engine)
157
+ end
158
+ end
159
+
160
+ class Privilege
161
+ @@privilege_objects = {}
162
+ def initialize (privilege, engine)
163
+ @privilege = privilege
164
+ @engine = engine
165
+ end
166
+
167
+ # Ancestor privileges are higher in the hierarchy.
168
+ # Doesn't take context into account.
169
+ def ancestors (priv_symbol = nil)
170
+ priv_symbol ||= @privilege
171
+ # context-specific?
172
+ (@engine.rev_priv_hierarchy[[priv_symbol, nil]] || []).
173
+ collect {|higher_priv| ancestors(higher_priv) }.flatten +
174
+ (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
175
+ end
176
+ def descendants (priv_symbol = nil)
177
+ priv_symbol ||= @privilege
178
+ # context-specific?
179
+ (@engine.privilege_hierarchy[priv_symbol] || []).
180
+ collect {|lower_priv, context| descendants(lower_priv) }.flatten +
181
+ (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
182
+ end
183
+
184
+ def rules
185
+ @rules ||= find_rules_for_privilege
186
+ end
187
+ def source_line
188
+ rules.empty? ? nil : rules.first.source_line
189
+ end
190
+ def source_file
191
+ rules.empty? ? nil : rules.first.source_file
192
+ end
193
+
194
+ def to_sym
195
+ @privilege
196
+ end
197
+ def self.for_sym (privilege_sym, engine)
198
+ @@privilege_objects[[privilege_sym, engine]] ||= new(privilege_sym, engine)
199
+ end
200
+
201
+ private
202
+ def find_rules_for_privilege
203
+ @engine.auth_rules.select {|rule| rule.privileges.include?(@privilege)}.
204
+ collect {|rule| Rule.for_rule(rule, @engine)}
205
+ end
206
+ end
207
+
208
+ class PrivilegesSet < Set
209
+ def initialize (*args)
210
+ if args.length > 2
211
+ @rule = args.shift
212
+ @engine = args.shift
213
+ end
214
+ super(*args)
215
+ end
216
+ def include? (privilege)
217
+ if privilege.is_a?(Symbol)
218
+ super(privilege_from_symbol(privilege))
219
+ else
220
+ super
221
+ end
222
+ end
223
+ def delete (privilege)
224
+ @rule.rule.privileges.delete(privilege.to_sym)
225
+ if privilege.is_a?(Symbol)
226
+ super(privilege_from_symbol(privilege))
227
+ else
228
+ super
229
+ end
230
+ end
231
+
232
+ def intersects? (privileges)
233
+ intersection(privileges).length > 0
234
+ end
235
+
236
+ private
237
+ def privilege_from_symbol (privilege_sym)
238
+ Privilege.for_sym(privilege_sym, @engine)
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,60 @@
1
+ # Authorization::AuthorizationHelper
2
+ require File.dirname(__FILE__) + '/authorization.rb'
3
+
4
+ module Authorization
5
+ module AuthorizationHelper
6
+
7
+ # If the current user meets the given privilege, permitted_to? returns true
8
+ # and yields to the optional block. The attribute checks that are defined
9
+ # in the authorization rules are only evaluated if an object is given
10
+ # for context.
11
+ #
12
+ # Examples:
13
+ # <% permitted_to? :create, :users do %>
14
+ # <%= link_to 'New', new_user_path %>
15
+ # <% end %>
16
+ # ...
17
+ # <% if permitted_to? :create, :users %>
18
+ # <%= link_to 'New', new_user_path %>
19
+ # <% else %>
20
+ # You are not allowed to create new users!
21
+ # <% end %>
22
+ # ...
23
+ # <% for user in @users %>
24
+ # <%= link_to 'Edit', edit_user_path(user) if permitted_to? :update, user %>
25
+ # <% end %>
26
+ #
27
+ # To pass in an object and override the context, you can use the optional
28
+ # options:
29
+ # permitted_to? :update, user, :context => :account
30
+ #
31
+ def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
32
+ controller.permitted_to?(privilege, object_or_sym, options, &block)
33
+ end
34
+
35
+ # While permitted_to? is used for authorization in views, in some cases
36
+ # content should only be shown to some users without being concerned
37
+ # with authorization. E.g. to only show the most relevant menu options
38
+ # to a certain group of users. That is what has_role? should be used for.
39
+ #
40
+ # Examples:
41
+ # <% has_role?(:sales) do %>
42
+ # <%= link_to 'All contacts', contacts_path %>
43
+ # <% end %>
44
+ # ...
45
+ # <% if has_role?(:sales) %>
46
+ # <%= link_to 'Customer contacts', contacts_path %>
47
+ # <% else %>
48
+ # ...
49
+ # <% end %>
50
+ #
51
+ def has_role? (*roles, &block)
52
+ controller.has_role?(*roles, &block)
53
+ end
54
+
55
+ # As has_role? except checks all roles included in the role hierarchy
56
+ def has_role_with_hierarchy?(*roles, &block)
57
+ controller.has_role_with_hierarchy?(*roles, &block)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,367 @@
1
+ # Authorization::AuthorizationInController
2
+ require File.dirname(__FILE__) + '/authorization.rb'
3
+
4
+ module Authorization
5
+ module AuthorizationInController
6
+
7
+ def self.included(base) # :nodoc:
8
+ base.extend(ClassMethods)
9
+ base.hide_action :authorization_engine, :permitted_to?,
10
+ :permitted_to!
11
+ end
12
+
13
+ DEFAULT_DENY = false
14
+
15
+ # Returns the Authorization::Engine for the current controller.
16
+ def authorization_engine
17
+ @authorization_engine ||= Authorization::Engine.instance
18
+ end
19
+
20
+ # If the current user meets the given privilege, permitted_to? returns true
21
+ # and yields to the optional block. The attribute checks that are defined
22
+ # in the authorization rules are only evaluated if an object is given
23
+ # for context.
24
+ #
25
+ # See examples for Authorization::AuthorizationHelper #permitted_to?
26
+ #
27
+ # If no object or context is specified, the controller_name is used as
28
+ # context.
29
+ #
30
+ def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
31
+ permitted_to!(privilege, object_or_sym, options.merge(:non_bang => true), &block)
32
+ end
33
+
34
+ # Works similar to the permitted_to? method, but
35
+ # throws the authorization exceptions, just like Engine#permit!
36
+ def permitted_to! (privilege, object_or_sym = nil, options = {}, &block)
37
+ context = object = nil
38
+ if object_or_sym.nil?
39
+ context = self.class.controller_name.to_sym
40
+ elsif object_or_sym.is_a?(Symbol)
41
+ context = object_or_sym
42
+ else
43
+ object = object_or_sym
44
+ end
45
+
46
+ non_bang = options.delete(:non_bang)
47
+ args = [
48
+ privilege,
49
+ {:user => current_user,
50
+ :object => object,
51
+ :context => context,
52
+ :skip_attribute_test => object.nil?}.merge(options)
53
+ ]
54
+ if non_bang
55
+ authorization_engine.permit?(*args, &block)
56
+ else
57
+ authorization_engine.permit!(*args, &block)
58
+ end
59
+ end
60
+
61
+ # While permitted_to? is used for authorization, in some cases
62
+ # content should only be shown to some users without being concerned
63
+ # with authorization. E.g. to only show the most relevant menu options
64
+ # to a certain group of users. That is what has_role? should be used for.
65
+ def has_role? (*roles, &block)
66
+ user_roles = authorization_engine.roles_for(current_user)
67
+ result = roles.all? do |role|
68
+ user_roles.include?(role)
69
+ end
70
+ yield if result and block_given?
71
+ result
72
+ end
73
+
74
+ # As has_role? except checks all roles included in the role hierarchy
75
+ def has_role_with_hierarchy?(*roles, &block)
76
+ user_roles = authorization_engine.roles_with_hierarchy_for(current_user)
77
+ result = roles.all? do |role|
78
+ user_roles.include?(role)
79
+ end
80
+ yield if result and block_given?
81
+ result
82
+ end
83
+
84
+
85
+ protected
86
+ def filter_access_filter # :nodoc:
87
+ permissions = self.class.all_filter_access_permissions
88
+ all_permissions = permissions.select {|p| p.actions.include?(:all)}
89
+ matching_permissions = permissions.select {|p| p.matches?(action_name)}
90
+ allowed = false
91
+ auth_exception = nil
92
+ begin
93
+ allowed = if !matching_permissions.empty?
94
+ matching_permissions.all? {|perm| perm.permit!(self)}
95
+ elsif !all_permissions.empty?
96
+ all_permissions.all? {|perm| perm.permit!(self)}
97
+ else
98
+ !DEFAULT_DENY
99
+ end
100
+ rescue AuthorizationError => e
101
+ auth_exception = e
102
+ end
103
+
104
+ unless allowed
105
+ if all_permissions.empty? and matching_permissions.empty?
106
+ logger.warn "Permission denied: No matching filter access " +
107
+ "rule found for #{self.class.controller_name}.#{action_name}"
108
+ elsif auth_exception
109
+ logger.info "Permission denied: #{auth_exception}"
110
+ end
111
+ if respond_to?(:permission_denied)
112
+ # permission_denied needs to render or redirect
113
+ send(:permission_denied)
114
+ else
115
+ send(:render, :text => "You are not allowed to access this action.",
116
+ :status => :forbidden)
117
+ end
118
+ end
119
+ end
120
+
121
+ module ClassMethods
122
+ #
123
+ # Defines a filter to be applied according to the authorization of the
124
+ # current user. Requires at least one symbol corresponding to an
125
+ # action as parameter. The special symbol :+all+ refers to all action.
126
+ # The all :+all+ statement is only employed if no specific statement is
127
+ # present.
128
+ # class UserController < ApplicationController
129
+ # filter_access_to :index
130
+ # filter_access_to :new, :edit
131
+ # filter_access_to :all
132
+ # ...
133
+ # end
134
+ #
135
+ # The default is to allow access unconditionally if no rule matches.
136
+ # Thus, including the +filter_access_to+ :+all+ statement is a good
137
+ # idea, implementing a default-deny policy.
138
+ #
139
+ # When the access is denied, the method +permission_denied+ is called
140
+ # on the current controller, if defined. Else, a simple "you are not
141
+ # allowed" string is output. Log.info is given more information on the
142
+ # reasons of denial.
143
+ #
144
+ # def permission_denied
145
+ # flash[:error] = 'Sorry, you are not allowed to the requested page.'
146
+ # respond_to do |format|
147
+ # format.html { redirect_to(:back) rescue redirect_to('/') }
148
+ # format.xml { head :unauthorized }
149
+ # format.js { head :unauthorized }
150
+ # end
151
+ # end
152
+ #
153
+ # By default, required privileges are infered from the action name and
154
+ # the controller name. Thus, in UserController :+edit+ requires
155
+ # :+edit+ +users+. To specify required privilege, use the option :+require+
156
+ # filter_access_to :new, :create, :require => :create, :context => :users
157
+ #
158
+ # Without the :+attribute_check+ option, no constraints from the
159
+ # authorization rules are enforced because for some actions (collections,
160
+ # +new+, +create+), there is no object to evaluate conditions against. To
161
+ # allow attribute checks on all actions, it is a common pattern to provide
162
+ # custom objects through +before_filters+:
163
+ # class BranchesController < ApplicationController
164
+ # before_filter :load_company
165
+ # before_filter :new_branch_from_company_and_params,
166
+ # :only => [:index, :new, :create]
167
+ # filter_access_to :all, :attribute_check => true
168
+ #
169
+ # protected
170
+ # def new_branch_from_company_and_params
171
+ # @branch = @company.branches.new(params[:branch])
172
+ # end
173
+ # end
174
+ # NOTE: +before_filters+ need to be defined before the first
175
+ # +filter_access_to+ call.
176
+ #
177
+ # For further customization, a custom filter expression may be formulated
178
+ # in a block, which is then evaluated in the context of the controller
179
+ # on a matching request. That is, for checking two objects, use the
180
+ # following:
181
+ # filter_access_to :merge do
182
+ # permitted_to!(:update, User.find(params[:original_id])) and
183
+ # permitted_to!(:delete, User.find(params[:id]))
184
+ # end
185
+ # The block should raise a Authorization::AuthorizationError or return
186
+ # false if the access is to be denied.
187
+ #
188
+ # Later calls to filter_access_to with overlapping actions overwrite
189
+ # previous ones for that action.
190
+ #
191
+ # All options:
192
+ # [:+require+]
193
+ # Privilege required; defaults to action_name
194
+ # [:+context+]
195
+ # The privilege's context, defaults to controller_name, pluralized.
196
+ # [:+namespace+]
197
+ # Prefix the default controller context with.
198
+ # * +true+: the model namespace(s) separated with underscores,
199
+ # * +Symbol+ or +String+: the given symbol or string
200
+ # * else: no prefix
201
+ # Example:
202
+ # filter_access_to :show, :namespace => true
203
+ # filter_access_to :delete, :namespace => :foo
204
+ # [:+attribute_check+]
205
+ # Enables the check of attributes defined in the authorization rules.
206
+ # Defaults to false. If enabled, filter_access_to will use a context
207
+ # object from one of the following sources (in that order):
208
+ # * the method from the :+load_method+ option,
209
+ # * an instance variable named after the singular of the context
210
+ # (by default from the controller name, e.g. @post for PostsController),
211
+ # * a find on the context model, using +params+[:id] as id value.
212
+ # Any of these methods will only be employed if :+attribute_check+
213
+ # is enabled.
214
+ # [:+model+]
215
+ # The data model to load a context object from. Defaults to the
216
+ # context, singularized.
217
+ # [:+load_method+]
218
+ # Specify a method by symbol or a Proc object which should be used
219
+ # to load the object. Both should return the loaded object.
220
+ # If a Proc object is given, e.g. by way of
221
+ # +lambda+, it is called in the instance of the controller.
222
+ # Example demonstrating the default behaviour:
223
+ # filter_access_to :show, :attribute_check => true,
224
+ # :load_method => lambda { User.find(params[:id]) }
225
+ #
226
+
227
+ def filter_access_to (*args, &filter_block)
228
+ options = args.last.is_a?(Hash) ? args.pop : {}
229
+ options = {
230
+ :require => nil,
231
+ :context => nil,
232
+ :namespace => nil,
233
+ :attribute_check => false,
234
+ :model => nil,
235
+ :load_method => nil
236
+ }.merge!(options)
237
+ privilege = options[:require]
238
+ context = options[:context]
239
+ actions = args.flatten
240
+
241
+ # collect permits in controller array for use in one before_filter
242
+ unless filter_chain.any? {|filter| filter.method == :filter_access_filter}
243
+ before_filter :filter_access_filter
244
+ end
245
+
246
+ filter_access_permissions.each do |perm|
247
+ perm.remove_actions(actions)
248
+ end
249
+ filter_access_permissions <<
250
+ ControllerPermission.new(actions, privilege, context,
251
+ options[:namespace],
252
+ options[:attribute_check],
253
+ options[:model],
254
+ options[:load_method],
255
+ filter_block)
256
+ end
257
+
258
+ # Collecting all the ControllerPermission objects from the controller
259
+ # hierarchy. Permissions for actions are overwritten by calls to
260
+ # filter_access_to in child controllers with the same action.
261
+ def all_filter_access_permissions # :nodoc:
262
+ ancestors.inject([]) do |perms, mod|
263
+ if mod.respond_to?(:filter_access_permissions)
264
+ perms +
265
+ mod.filter_access_permissions.collect do |p1|
266
+ p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions})
267
+ end
268
+ else
269
+ perms
270
+ end
271
+ end
272
+ end
273
+
274
+ protected
275
+ def filter_access_permissions # :nodoc:
276
+ unless filter_access_permissions?
277
+ ancestors[1..-1].reverse.each do |mod|
278
+ mod.filter_access_permissions if mod.respond_to?(:filter_access_permissions)
279
+ end
280
+ end
281
+ class_variable_set(:@@declarative_authorization_permissions, {}) unless filter_access_permissions?
282
+ class_variable_get(:@@declarative_authorization_permissions)[self.name] ||= []
283
+ end
284
+
285
+ def filter_access_permissions? # :nodoc:
286
+ class_variable_defined?(:@@declarative_authorization_permissions)
287
+ end
288
+ end
289
+ end
290
+
291
+ class ControllerPermission # :nodoc:
292
+ attr_reader :actions, :privilege, :context, :namespace, :attribute_check
293
+ def initialize (actions, privilege, context, namespace, attribute_check = false,
294
+ load_object_model = nil, load_object_method = nil,
295
+ filter_block = nil)
296
+ @actions = actions.to_set
297
+ @privilege = privilege
298
+ @context = context
299
+ @namespace = namespace
300
+ @load_object_model = load_object_model
301
+ @load_object_method = load_object_method
302
+ @filter_block = filter_block
303
+ @attribute_check = attribute_check
304
+ end
305
+
306
+ def controller_context(contr)
307
+ case @namespace
308
+ when true
309
+ "#{contr.class.name.gsub(/::/, "_").gsub(/Controller$/, "").underscore}".to_sym
310
+ when String, Symbol
311
+ "#{@namespace.to_s}_#{contr.class.controller_name}".to_sym
312
+ else
313
+ contr.class.controller_name.to_sym
314
+ end
315
+ end
316
+
317
+ def matches? (action_name)
318
+ @actions.include?(action_name.to_sym)
319
+ end
320
+
321
+ def permit! (contr)
322
+ if @filter_block
323
+ return contr.instance_eval(&@filter_block)
324
+ end
325
+ context = @context || controller_context(contr)
326
+ object = @attribute_check ? load_object(contr, context) : nil
327
+ privilege = @privilege || :"#{contr.action_name}"
328
+
329
+ #puts "Trying permit?(#{privilege.inspect}, "
330
+ #puts " :user => #{contr.send(:current_user).inspect}, "
331
+ #puts " :object => #{object.inspect},"
332
+ #puts " :skip_attribute_test => #{!@attribute_check},"
333
+ #puts " :context => #{contr.class.controller_name.pluralize.to_sym})"
334
+ res = contr.authorization_engine.permit!(privilege,
335
+ :user => contr.send(:current_user),
336
+ :object => object,
337
+ :skip_attribute_test => !@attribute_check,
338
+ :context => context)
339
+ #puts "permit? result: #{res.inspect}"
340
+ res
341
+ end
342
+
343
+ def remove_actions (actions)
344
+ @actions -= actions
345
+ self
346
+ end
347
+
348
+ private
349
+ def load_object(contr, context)
350
+ if @load_object_method and @load_object_method.is_a?(Symbol)
351
+ contr.send(@load_object_method)
352
+ elsif @load_object_method and @load_object_method.is_a?(Proc)
353
+ contr.instance_eval(&@load_object_method)
354
+ else
355
+ load_object_model = @load_object_model || context.to_s.classify.constantize
356
+ instance_var = :"@#{load_object_model.name.underscore}"
357
+ object = contr.instance_variable_get(instance_var)
358
+ unless object
359
+ # catch ActiveRecord::RecordNotFound?
360
+ object = load_object_model.find(contr.params[:id])
361
+ contr.instance_variable_set(instance_var, object)
362
+ end
363
+ object
364
+ end
365
+ end
366
+ end
367
+ end