culturecode-cancan 2.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +381 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +108 -0
  6. data/Rakefile +18 -0
  7. data/init.rb +1 -0
  8. data/lib/cancan.rb +13 -0
  9. data/lib/cancan/ability.rb +348 -0
  10. data/lib/cancan/controller_additions.rb +392 -0
  11. data/lib/cancan/controller_resource.rb +266 -0
  12. data/lib/cancan/exceptions.rb +53 -0
  13. data/lib/cancan/inherited_resource.rb +20 -0
  14. data/lib/cancan/matchers.rb +14 -0
  15. data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
  16. data/lib/cancan/model_adapters/active_record_adapter.rb +172 -0
  17. data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
  18. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  19. data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
  20. data/lib/cancan/model_additions.rb +29 -0
  21. data/lib/cancan/rule.rb +178 -0
  22. data/lib/generators/cancan/ability/USAGE +5 -0
  23. data/lib/generators/cancan/ability/ability_generator.rb +16 -0
  24. data/lib/generators/cancan/ability/templates/ability.rb +24 -0
  25. data/lib/generators/cancan/ability/templates/ability_spec.rb +16 -0
  26. data/lib/generators/cancan/ability/templates/ability_test.rb +10 -0
  27. data/spec/README.rdoc +28 -0
  28. data/spec/cancan/ability_spec.rb +541 -0
  29. data/spec/cancan/controller_additions_spec.rb +118 -0
  30. data/spec/cancan/controller_resource_spec.rb +551 -0
  31. data/spec/cancan/exceptions_spec.rb +58 -0
  32. data/spec/cancan/inherited_resource_spec.rb +58 -0
  33. data/spec/cancan/matchers_spec.rb +33 -0
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +278 -0
  35. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +120 -0
  36. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  37. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
  38. data/spec/cancan/rule_spec.rb +55 -0
  39. data/spec/matchers.rb +13 -0
  40. data/spec/spec_helper.rb +49 -0
  41. metadata +194 -0
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
8
+ end
9
+
10
+ desc "Run specs for all adapters"
11
+ task :spec_all do
12
+ %w[active_record data_mapper mongoid].each do |model_adapter|
13
+ puts "MODEL_ADAPTER = #{model_adapter}"
14
+ system "rake spec MODEL_ADAPTER=#{model_adapter}"
15
+ end
16
+ end
17
+
18
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'cancan'
@@ -0,0 +1,13 @@
1
+ require 'cancan/ability'
2
+ require 'cancan/rule'
3
+ require 'cancan/controller_resource'
4
+ require 'cancan/controller_additions'
5
+ require 'cancan/model_additions'
6
+ require 'cancan/exceptions'
7
+ require 'cancan/inherited_resource'
8
+
9
+ require 'cancan/model_adapters/abstract_adapter'
10
+ require 'cancan/model_adapters/default_adapter'
11
+ require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
12
+ require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
13
+ require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
@@ -0,0 +1,348 @@
1
+ module CanCan
2
+
3
+ # This module is designed to be included into an Ability class. This will
4
+ # provide the "can" methods for defining and checking abilities.
5
+ #
6
+ # class Ability
7
+ # include CanCan::Ability
8
+ #
9
+ # def initialize(user)
10
+ # if user.admin?
11
+ # can :access, :all
12
+ # else
13
+ # can :read, :all
14
+ # end
15
+ # end
16
+ # end
17
+ #
18
+ module Ability
19
+ # Check if the user has permission to perform a given action on an object.
20
+ #
21
+ # can? :destroy, @project
22
+ #
23
+ # You can also pass the class instead of an instance (if you don't have one handy).
24
+ #
25
+ # can? :create, :projects
26
+ #
27
+ # Nested resources can be passed through a hash, this way conditions which are
28
+ # dependent upon the association will work when using a class.
29
+ #
30
+ # can? :create, @category => :projects
31
+ #
32
+ # Not only can you use the can? method in the controller and view (see ControllerAdditions),
33
+ # but you can also call it directly on an ability instance.
34
+ #
35
+ # ability.can? :destroy, @project
36
+ #
37
+ # This makes testing a user's abilities very easy.
38
+ #
39
+ # def test "user can only destroy projects which he owns"
40
+ # user = User.new
41
+ # ability = Ability.new(user)
42
+ # assert ability.can?(:destroy, Project.new(:user => user))
43
+ # assert ability.cannot?(:destroy, Project.new)
44
+ # end
45
+ #
46
+ # Also see the RSpec Matchers to aid in testing.
47
+ def can?(action, subject, attribute = nil)
48
+ match = relevant_rules_for_match(action, subject, attribute).detect do |rule|
49
+ rule.matches_conditions?(action, subject, attribute)
50
+ end
51
+ match ? match.base_behavior : false
52
+ end
53
+
54
+ # Convenience method which works the same as "can?" but returns the opposite value.
55
+ #
56
+ # cannot? :destroy, @project
57
+ #
58
+ def cannot?(*args)
59
+ !can?(*args)
60
+ end
61
+
62
+ # Defines which abilities are allowed using two arguments. The first one is the action
63
+ # you're setting the permission for, the second one is the class of object you're setting it on.
64
+ #
65
+ # can :update, :articles
66
+ #
67
+ # You can pass an array for either of these parameters to match any one.
68
+ # Here the user has the ability to update or destroy both articles and comments.
69
+ #
70
+ # can [:update, :destroy], [:articles, :comments]
71
+ #
72
+ # You can pass :all to match any object and :access to match any action. Here are some examples.
73
+ #
74
+ # can :access, :all
75
+ # can :update, :all
76
+ # can :access, :projects
77
+ #
78
+ # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
79
+ #
80
+ # can :read, :projects, :active => true, :user_id => user.id
81
+ #
82
+ # See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
83
+ # are also used for initial attributes when building a record in ControllerAdditions#load_resource.
84
+ #
85
+ # If the conditions hash does not give you enough control over defining abilities, you can use a block
86
+ # along with any Ruby code you want.
87
+ #
88
+ # can :update, :projects do |project|
89
+ # project.groups.include?(user.group)
90
+ # end
91
+ #
92
+ # If the block returns true then the user has that :update ability for that project, otherwise he
93
+ # will be denied access. The downside to using a block is that it cannot be used to generate
94
+ # conditions for database queries.
95
+ #
96
+ # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol.
97
+ #
98
+ # can :update, :projects, :priority => 3
99
+ # can? :update, :projects # => true
100
+ #
101
+ # If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
102
+ # block will always be executed. This allows you to override the full behavior if the permissions are
103
+ # defined in an external source such as the database.
104
+ #
105
+ # can do |action, subject, object|
106
+ # # check the database and return true/false
107
+ # end
108
+ #
109
+ def can(*args, &block)
110
+ rules << Rule.new(true, *args, &block)
111
+ end
112
+
113
+ # Defines an ability which cannot be done. Accepts the same arguments as "can".
114
+ #
115
+ # can :read, :all
116
+ # cannot :read, Comment
117
+ #
118
+ # A block can be passed just like "can", however if the logic is complex it is recommended
119
+ # to use the "can" method.
120
+ #
121
+ # cannot :read, :projects do |product|
122
+ # product.invisible?
123
+ # end
124
+ #
125
+ def cannot(*args, &block)
126
+ rules << Rule.new(false, *args, &block)
127
+ end
128
+
129
+ # Alias one or more actions into another one.
130
+ #
131
+ # alias_action :update, :destroy, :to => :modify
132
+ # can :modify, :comments
133
+ #
134
+ # Then :modify permission will apply to both :update and :destroy requests.
135
+ #
136
+ # can? :update, :comments # => true
137
+ # can? :destroy, :comments # => true
138
+ #
139
+ # This only works in one direction. Passing the aliased action into the "can?" call
140
+ # will not work because aliases are meant to generate more generic actions.
141
+ #
142
+ # alias_action :update, :destroy, :to => :modify
143
+ # can :update, :comments
144
+ # can? :modify, :comments # => false
145
+ #
146
+ # The following aliases are added by default for conveniently mapping common controller actions.
147
+ #
148
+ # alias_action :index, :show, :to => :read
149
+ # alias_action :new, :to => :create
150
+ # alias_action :edit, :to => :update
151
+ #
152
+ # This way one can use params[:action] in the controller to determine the permission.
153
+ def alias_action(*args)
154
+ target = args.pop[:to]
155
+ aliases[:actions][target] ||= []
156
+ aliases[:actions][target] += args
157
+ end
158
+
159
+ # Alias one or more subjects into another one.
160
+ #
161
+ # alias_subject :admins, :moderators, :to => :users
162
+ # can :update, :users
163
+ #
164
+ # Then :modify permission will apply to both :update and :destroy requests.
165
+ #
166
+ # can? :update, :admins # => true
167
+ # can? :update, :moderators # => true
168
+ #
169
+ # This only works in one direction. Passing the aliased subject into the "can?" call
170
+ # will not work because aliases are meant to generate more generic subjects.
171
+ #
172
+ # alias_subject :admins, :moderators, :to => :users
173
+ # can :update, :admins
174
+ # can? :update, :users # => false
175
+ #
176
+ def alias_subject(*args)
177
+ target = args.pop[:to]
178
+ aliases[:subjects][target] ||= []
179
+ aliases[:subjects][target] += args
180
+ end
181
+
182
+ # Returns a hash of action and subject aliases.
183
+ def aliases
184
+ @aliases ||= default_aliases
185
+ end
186
+
187
+ # Removes previously aliased actions or subjects including the defaults.
188
+ def clear_aliases
189
+ aliases[:actions] = {}
190
+ aliases[:subjects] = {}
191
+ end
192
+
193
+ def model_adapter(model_class, action)
194
+ adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
195
+ adapter_class.new(model_class, relevant_rules_for_query(action, model_class.to_s.underscore.pluralize.to_sym))
196
+ end
197
+
198
+ # See ControllerAdditions#authorize! for documentation.
199
+ def authorize!(action, subject, *args)
200
+ message = nil
201
+ if args.last.kind_of?(Hash)
202
+ message = args.pop[:message]
203
+ end
204
+ attribute = args.first
205
+ if cannot?(action, subject, *args)
206
+ message ||= unauthorized_message(action, subject)
207
+ raise Unauthorized.new(message, action, subject)
208
+ elsif sufficient_attribute_check?(action, subject, attribute) && sufficient_condition_check?(action, subject)
209
+ fully_authorized!(action, subject)
210
+ end
211
+ subject
212
+ end
213
+
214
+ def unauthorized_message(action, subject)
215
+ keys = unauthorized_message_keys(action, subject)
216
+ variables = {:action => action.to_s}
217
+ variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
218
+ message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
219
+ message.blank? ? nil : message
220
+ end
221
+
222
+ def attributes_for(action, subject)
223
+ attributes = {}
224
+ relevant_rules(action, subject).map do |rule|
225
+ attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
226
+ end
227
+ attributes
228
+ end
229
+
230
+ def has_block?(action, subject)
231
+ relevant_rules(action, subject).any?(&:only_block?)
232
+ end
233
+
234
+ def has_raw_sql?(action, subject)
235
+ relevant_rules(action, subject).any?(&:only_raw_sql?)
236
+ end
237
+
238
+ def has_instance_conditions?(action, subject)
239
+ relevant_rules(action, subject).any?(&:instance_conditions?)
240
+ end
241
+
242
+ def has_attributes?(action, subject)
243
+ relevant_rules(action, subject).any?(&:attributes?)
244
+ end
245
+
246
+ def fully_authorized?(action, subject)
247
+ @fully_authorized ||= []
248
+ @fully_authorized.include? [action.to_sym, subject.to_sym]
249
+ end
250
+
251
+ def fully_authorized!(action, subject)
252
+ subject = subject.class.to_s.underscore.pluralize.to_sym unless subject.kind_of?(Symbol) || subject.kind_of?(String)
253
+ @fully_authorized ||= []
254
+ @fully_authorized << [action.to_sym, subject.to_sym]
255
+ end
256
+
257
+ def merge(ability)
258
+ ability.send(:rules).each do |rule|
259
+ rules << rule.dup
260
+ end
261
+ self
262
+ end
263
+
264
+ private
265
+
266
+ def unauthorized_message_keys(action, subject)
267
+ subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.pluralize)
268
+ [aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject|
269
+ [aliases_for(:actions, action.to_sym), :access].flatten.map do |try_action|
270
+ :"#{try_action}.#{try_subject}"
271
+ end
272
+ end.flatten
273
+ end
274
+
275
+ def sufficient_attribute_check?(action, subject, attribute)
276
+ !(%w[create update].include?(action.to_s) && attribute.nil? && has_attributes?(action, subject))
277
+ end
278
+
279
+ def sufficient_condition_check?(action, subject)
280
+ !((subject.kind_of?(Symbol) || subject.kind_of?(String)) && has_instance_conditions?(action, subject))
281
+ end
282
+
283
+ # Accepts an array of actions and returns an array of actions which match.
284
+ # This should be called before "matches?" and other checking methods since they
285
+ # rely on the actions to be expanded.
286
+ def expand_aliases(type, items)
287
+ items.map do |item|
288
+ aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item
289
+ end.flatten
290
+ end
291
+
292
+ # Given an action, it will try to find all of the actions which are aliased to it.
293
+ # This does the opposite kind of lookup as expand_aliases.
294
+ def aliases_for(type, action)
295
+ results = [action]
296
+ aliases[type].each do |aliased_action, actions|
297
+ results += aliases_for(type, aliased_action) if actions.include? action
298
+ end
299
+ results
300
+ end
301
+
302
+ def rules
303
+ @rules ||= []
304
+ end
305
+
306
+ # Returns an array of Rule instances which match the action and subject
307
+ # This does not take into consideration any hash conditions or block statements
308
+ def relevant_rules(action, subject, attribute = nil)
309
+ specificity = 0
310
+ rules.reverse.each_with_object([]) do |rule, relevant_rules|
311
+ rule.expanded_actions = expand_aliases(:actions, rule.actions)
312
+ rule.expanded_subjects = expand_aliases(:subjects, rule.subjects)
313
+ if rule.relevant?(action, subject, attribute) && rule.specificity >= specificity
314
+ specificity = rule.specificity if rule.base_behavior
315
+ relevant_rules << rule
316
+ end
317
+ end
318
+ end
319
+
320
+ def relevant_rules_for_match(action, subject, attribute)
321
+ relevant_rules(action, subject, attribute).each do |rule|
322
+ if rule.only_raw_sql?
323
+ raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
324
+ end
325
+ end
326
+ end
327
+
328
+ def relevant_rules_for_query(action, subject)
329
+ relevant_rules(action, subject, nil).each do |rule|
330
+ if rule.only_block?
331
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
332
+ end
333
+ end
334
+ end
335
+
336
+ def default_aliases
337
+ {
338
+ :subjects => {},
339
+ :actions => {
340
+ :read => [:index, :show],
341
+ :create => [:new],
342
+ :update => [:edit],
343
+ :destroy => [:delete],
344
+ }
345
+ }
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,392 @@
1
+ module CanCan
2
+
3
+ # This module is automatically included into all controllers.
4
+ # It also makes the "can?" and "cannot?" methods available to all views.
5
+ module ControllerAdditions
6
+ module ClassMethods
7
+ # Sets up a before filter which loads and authorizes the current resource. This performs both
8
+ # load_resource and authorize_resource and accepts the same arguments. See those methods for details.
9
+ #
10
+ # class BooksController < ApplicationController
11
+ # load_and_authorize_resource
12
+ # end
13
+ #
14
+ def load_and_authorize_resource(*args)
15
+ cancan_resource_class.add_before_filter(self, {:load => true, :authorize => true}, *args)
16
+ end
17
+
18
+ # Sets up a before filter which loads the model resource into an instance variable.
19
+ # For example, given an ArticlesController it will load the current article into the @article
20
+ # instance variable. It does this by either calling Article.find(params[:id]) or
21
+ # Article.new(params[:article]) depending upon the action. The index action will
22
+ # automatically set @articles to Article.accessible_by(current_ability).
23
+ #
24
+ # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
25
+ # the initial attributes based on these conditions. This way these actions will satisfy
26
+ # the ability restrictions.
27
+ #
28
+ # Call this method directly on the controller class.
29
+ #
30
+ # class BooksController < ApplicationController
31
+ # load_resource
32
+ # end
33
+ #
34
+ # A resource is not loaded if the instance variable is already set. This makes it easy to override
35
+ # the behavior through a before_filter on certain actions.
36
+ #
37
+ # class BooksController < ApplicationController
38
+ # before_filter :find_book_by_permalink, :only => :show
39
+ # load_resource
40
+ #
41
+ # private
42
+ #
43
+ # def find_book_by_permalink
44
+ # @book = Book.find_by_permalink!(params[:id)
45
+ # end
46
+ # end
47
+ #
48
+ # If a name is provided which does not match the controller it assumes it is a parent resource. Child
49
+ # resources can then be loaded through it.
50
+ #
51
+ # class BooksController < ApplicationController
52
+ # load_resource :author
53
+ # load_resource :book, :through => :author
54
+ # end
55
+ #
56
+ # Here the author resource will be loaded before each action using params[:author_id]. The book resource
57
+ # will then be loaded through the @author instance variable.
58
+ #
59
+ # That first argument is optional and will default to the singular name of the controller.
60
+ # A hash of options (see below) can also be passed to this method to further customize it.
61
+ #
62
+ # See load_and_authorize_resource to automatically authorize the resource too.
63
+ #
64
+ # Options:
65
+ # [:+only+]
66
+ # Only applies before filter to given actions.
67
+ #
68
+ # [:+except+]
69
+ # Does not apply before filter to given actions.
70
+ #
71
+ # [:+through+]
72
+ # Load this resource through another one. This should match the name of the parent instance variable or method.
73
+ #
74
+ # [:+through_association+]
75
+ # The name of the association to fetch the child records through the parent resource. This is normally not needed
76
+ # because it defaults to the pluralized resource name.
77
+ #
78
+ # [:+shallow+]
79
+ # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
80
+ #
81
+ # [:+singleton+]
82
+ # Pass +true+ if this is a singleton resource through a +has_one+ association.
83
+ #
84
+ # [:+parent+]
85
+ # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
86
+ # name is given which does not match the controller.
87
+ #
88
+ # [:+class+]
89
+ # The class to use for the model (string or constant).
90
+ #
91
+ # [:+instance_name+]
92
+ # The name of the instance variable to load the resource into.
93
+ #
94
+ # [:+find_by+]
95
+ # Find using a different attribute other than id. For example.
96
+ #
97
+ # load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
98
+ #
99
+ # [:+collection+]
100
+ # Specify which actions are resource collection actions in addition to :+index+. This
101
+ # is usually not necessary because it will try to guess depending on if the id param is present.
102
+ #
103
+ # load_resource :collection => [:sort, :list]
104
+ #
105
+ # [:+new+]
106
+ # Specify which actions are new resource actions in addition to :+new+ and :+create+.
107
+ # Pass an action name into here if you would like to build a new resource instead of
108
+ # fetch one.
109
+ #
110
+ # load_resource :new => :build
111
+ #
112
+ # [:+prepend+]
113
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
114
+ #
115
+ def load_resource(*args)
116
+ raise ImplementationRemoved, "The load_resource method has been removed, use load_and_authorize_resource instead."
117
+ cancan_resource_class.add_before_filter(self, {:load => true}, *args)
118
+ end
119
+
120
+ # Sets up a before filter which authorizes the resource using the instance variable.
121
+ # For example, if you have an ArticlesController it will check the @article instance variable
122
+ # and ensure the user can perform the current action on it. Under the hood it is doing
123
+ # something like the following.
124
+ #
125
+ # authorize!(params[:action].to_sym, @article || Article)
126
+ #
127
+ # Call this method directly on the controller class.
128
+ #
129
+ # class BooksController < ApplicationController
130
+ # authorize_resource
131
+ # end
132
+ #
133
+ # If you pass in the name of a resource which does not match the controller it will assume
134
+ # it is a parent resource.
135
+ #
136
+ # class BooksController < ApplicationController
137
+ # authorize_resource :author
138
+ # authorize_resource :book
139
+ # end
140
+ #
141
+ # Here it will authorize :+show+, @+author+ on every action before authorizing the book.
142
+ #
143
+ # That first argument is optional and will default to the singular name of the controller.
144
+ # A hash of options (see below) can also be passed to this method to further customize it.
145
+ #
146
+ # See load_and_authorize_resource to automatically load the resource too.
147
+ #
148
+ # Options:
149
+ # [:+only+]
150
+ # Only applies before filter to given actions.
151
+ #
152
+ # [:+except+]
153
+ # Does not apply before filter to given actions.
154
+ #
155
+ # [:+singleton+]
156
+ # Pass +true+ if this is a singleton resource through a +has_one+ association.
157
+ #
158
+ # [:+parent+]
159
+ # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
160
+ # name is given which does not match the controller.
161
+ #
162
+ # [:+class+]
163
+ # The class to use for the model (string or constant). This passed in when the instance variable is not set.
164
+ # Pass +false+ if there is no associated class for this resource and it will use a symbol of the resource name.
165
+ #
166
+ # [:+instance_name+]
167
+ # The name of the instance variable for this resource.
168
+ #
169
+ # [:+through+]
170
+ # Authorize conditions on this parent resource when instance isn't available.
171
+ #
172
+ # [:+prepend+]
173
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
174
+ #
175
+ def authorize_resource(*args)
176
+ raise ImplementationRemoved, "The authorize_resource method has been removed, use load_and_authorize_resource instead."
177
+ cancan_resource_class.add_before_filter(self, {:authorize => true}, *args)
178
+ end
179
+
180
+ # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
181
+ # useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions
182
+ # to skip the effects on. It will apply to all actions by default.
183
+ #
184
+ # class ProjectsController < SomeOtherController
185
+ # skip_load_and_authorize_resource :only => :index
186
+ # end
187
+ #
188
+ # You can also pass the resource name as the first argument to skip that resource.
189
+ def skip_load_and_authorize_resource(*args)
190
+ skip_load_resource(*args)
191
+ skip_authorize_resource(*args)
192
+ end
193
+
194
+ # Skip the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
195
+ # only do authorization on certain actions. You can pass :only and :except options to specify which actions to
196
+ # skip the effects on. It will apply to all actions by default.
197
+ #
198
+ # class ProjectsController < ApplicationController
199
+ # load_and_authorize_resource
200
+ # skip_load_resource :only => :index
201
+ # end
202
+ #
203
+ # You can also pass the resource name as the first argument to skip that resource.
204
+ def skip_load_resource(*args)
205
+ raise ImplementationRemoved, "The skip_load_resource method has been removed, use skip_load_and_authorize_resource instead."
206
+ options = args.extract_options!
207
+ name = args.first
208
+ cancan_skipper[:load][name] = options
209
+ end
210
+
211
+ # Skip the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
212
+ # only do loading on certain actions. You can pass :only and :except options to specify which actions to
213
+ # skip the effects on. It will apply to all actions by default.
214
+ #
215
+ # class ProjectsController < ApplicationController
216
+ # load_and_authorize_resource
217
+ # skip_authorize_resource :only => :index
218
+ # end
219
+ #
220
+ # You can also pass the resource name as the first argument to skip that resource.
221
+ def skip_authorize_resource(*args)
222
+ raise ImplementationRemoved, "The skip_authorize_resource method has been removed, use skip_load_and_authorize_resource instead."
223
+ options = args.extract_options!
224
+ name = args.first
225
+ cancan_skipper[:authorize][name] = options
226
+ end
227
+
228
+ # Add this to a controller to automatically perform authorization on every action.
229
+ #
230
+ # class ApplicationController < ActionController::Base
231
+ # enable_authorization
232
+ # end
233
+ #
234
+ # Internally it does this in a before_filter for every action.
235
+ #
236
+ # authorize! params[:action], params[:controller]
237
+ #
238
+ # If you need to "skip" authorization in a given controller, it is best to enable :+access+ to it in the +Ability+.
239
+ #
240
+ # Options:
241
+ # [:+only+]
242
+ # Only applies to given actions.
243
+ #
244
+ # [:+except+]
245
+ # Does not apply to given actions.
246
+ #
247
+ # [:+if+]
248
+ # Supply the name of a controller method to be called. The authorization only takes place if this returns true.
249
+ #
250
+ # enable_authorization :if => :admin_controller?
251
+ #
252
+ # [:+unless+]
253
+ # Supply the name of a controller method to be called. The authorization only takes place if this returns false.
254
+ #
255
+ # enable_authorization :unless => :devise_controller?
256
+ #
257
+ def enable_authorization(options = {}, &block)
258
+ before_filter(options.slice(:only, :except)) do |controller|
259
+ break if options[:if] && !controller.send(options[:if])
260
+ break if options[:unless] && controller.send(options[:unless])
261
+ controller.authorize! controller.params[:action], controller.params[:controller]
262
+ end
263
+ after_filter(options.slice(:only, :except)) do |controller|
264
+ break if options[:if] && !controller.send(options[:if])
265
+ break if options[:unless] && controller.send(options[:unless])
266
+ unless controller.current_ability.fully_authorized? controller.params[:action], controller.params[:controller]
267
+ raise CanCan::InsufficientAuthorizationCheck, "Authorization check is not sufficient for this action. This is probably because you have conditions or attributes defined in Ability and are not checking for them in the action. One way to solve this is adding load_and_authorize_resource to this controller."
268
+ end
269
+ end
270
+ rescue_from(CanCan::Unauthorized, &block) if block
271
+ end
272
+
273
+ def cancan_resource_class
274
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
275
+ InheritedResource
276
+ else
277
+ ControllerResource
278
+ end
279
+ end
280
+
281
+ def check_authorization(options = {})
282
+ raise ImplementationRemoved, "The check_authorization method has been removed, use enable_authorization instead."
283
+ end
284
+
285
+ def skip_authorization_check(*args)
286
+ raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
287
+ end
288
+
289
+ def cancan_skipper
290
+ raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
291
+ end
292
+ end
293
+
294
+ def self.included(base)
295
+ base.extend ClassMethods
296
+ base.helper_method :can?, :cannot?, :current_ability
297
+ end
298
+
299
+ # Raises a CanCan::Unauthorized exception if the current_ability cannot
300
+ # perform the given action. This is usually called in a controller action or
301
+ # before filter to perform the authorization.
302
+ #
303
+ # def show
304
+ # @article = Article.find(params[:id])
305
+ # authorize! :read, @article
306
+ # end
307
+ #
308
+ # A :message option can be passed to specify a different message.
309
+ #
310
+ # authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
311
+ #
312
+ # You can also use I18n to customize the message. Action aliases defined in Ability work here.
313
+ #
314
+ # en:
315
+ # unauthorized:
316
+ # manage:
317
+ # all: "Not authorized to %{action} %{subject}."
318
+ # user: "Not allowed to manage other user accounts."
319
+ # update:
320
+ # project: "Not allowed to update this project."
321
+ #
322
+ # You can rescue from the exception in the controller to customize how unauthorized
323
+ # access is displayed to the user.
324
+ #
325
+ # class ApplicationController < ActionController::Base
326
+ # rescue_from CanCan::Unauthorized do |exception|
327
+ # redirect_to root_url, :alert => exception.message
328
+ # end
329
+ # end
330
+ #
331
+ # See the CanCan::Unauthorized exception for more details on working with the exception.
332
+ #
333
+ # See the load_and_authorize_resource method to automatically add the authorize! behavior
334
+ # to the default RESTful actions.
335
+ def authorize!(*args)
336
+ @_authorized = true
337
+ current_ability.authorize!(*args)
338
+ end
339
+
340
+ # Creates and returns the current user's ability and caches it. If you
341
+ # want to override how the Ability is defined then this is the place.
342
+ # Just define the method in the controller to change behavior.
343
+ #
344
+ # def current_ability
345
+ # # instead of Ability.new(current_user)
346
+ # @current_ability ||= UserAbility.new(current_account)
347
+ # end
348
+ #
349
+ # Notice it is important to cache the ability object so it is not
350
+ # recreated every time.
351
+ def current_ability
352
+ @current_ability ||= ::Ability.new(current_user)
353
+ end
354
+
355
+ # Use in the controller or view to check the user's permission for a given action
356
+ # and object.
357
+ #
358
+ # can? :destroy, @project
359
+ #
360
+ # You can also pass the class instead of an instance (if you don't have one handy).
361
+ #
362
+ # <% if can? :create, Project %>
363
+ # <%= link_to "New Project", new_project_path %>
364
+ # <% end %>
365
+ #
366
+ # If it's a nested resource, you can pass the parent instance in a hash. This way it will
367
+ # check conditions which reach through that association.
368
+ #
369
+ # <% if can? :create, @category => Project %>
370
+ # <%= link_to "New Project", new_project_path %>
371
+ # <% end %>
372
+ #
373
+ # This simply calls "can?" on the current_ability. See Ability#can?.
374
+ def can?(*args)
375
+ current_ability.can?(*args)
376
+ end
377
+
378
+ # Convenience method which works the same as "can?" but returns the opposite value.
379
+ #
380
+ # cannot? :destroy, @project
381
+ #
382
+ def cannot?(*args)
383
+ current_ability.cannot?(*args)
384
+ end
385
+ end
386
+ end
387
+
388
+ if defined? ActionController::Base
389
+ ActionController::Base.class_eval do
390
+ include CanCan::ControllerAdditions
391
+ end
392
+ end