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