uhees-declarative_authorization 0.3.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 (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