cancan 1.3.4 → 1.4.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.
- data/CHANGELOG.rdoc +52 -0
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.rdoc +31 -21
- data/Rakefile +5 -8
- data/lib/cancan/ability.rb +99 -32
- data/lib/cancan/active_record_additions.rb +1 -1
- data/lib/cancan/can_definition.rb +43 -14
- data/lib/cancan/controller_additions.rb +84 -12
- data/lib/cancan/controller_resource.rb +70 -12
- data/lib/cancan/exceptions.rb +5 -1
- data/lib/cancan/inherited_resource.rb +18 -0
- data/lib/cancan.rb +1 -0
- data/spec/cancan/ability_spec.rb +184 -59
- data/spec/cancan/active_record_additions_spec.rb +25 -1
- data/spec/cancan/can_definition_spec.rb +1 -0
- data/spec/cancan/controller_additions_spec.rb +34 -26
- data/spec/cancan/controller_resource_spec.rb +162 -87
- data/spec/cancan/inherited_resource_spec.rb +40 -0
- data/spec/cancan/query_spec.rb +48 -48
- data/spec/matchers.rb +1 -1
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -9
- metadata +79 -6
|
@@ -12,14 +12,18 @@ module CanCan
|
|
|
12
12
|
# end
|
|
13
13
|
#
|
|
14
14
|
def load_and_authorize_resource(*args)
|
|
15
|
-
|
|
15
|
+
cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Sets up a before filter which loads the model resource into an instance variable.
|
|
19
19
|
# For example, given an ArticlesController it will load the current article into the @article
|
|
20
20
|
# instance variable. It does this by either calling Article.find(params[:id]) or
|
|
21
|
-
# Article.new(params[:article]) depending upon the action.
|
|
22
|
-
#
|
|
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.
|
|
23
27
|
#
|
|
24
28
|
# Call this method directly on the controller class.
|
|
25
29
|
#
|
|
@@ -65,7 +69,14 @@ module CanCan
|
|
|
65
69
|
# Does not apply before filter to given actions.
|
|
66
70
|
#
|
|
67
71
|
# [:+through+]
|
|
68
|
-
# Load this resource through another one. This should match the name of the parent instance variable.
|
|
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+.
|
|
69
80
|
#
|
|
70
81
|
# [:+singleton+]
|
|
71
82
|
# Pass +true+ if this is a singleton resource through a +has_one+ association.
|
|
@@ -99,7 +110,7 @@ module CanCan
|
|
|
99
110
|
# load_resource :new => :build
|
|
100
111
|
#
|
|
101
112
|
def load_resource(*args)
|
|
102
|
-
|
|
113
|
+
cancan_resource_class.add_before_filter(self, :load_resource, *args)
|
|
103
114
|
end
|
|
104
115
|
|
|
105
116
|
# Sets up a before filter which authorizes the resource using the instance variable.
|
|
@@ -148,8 +159,55 @@ module CanCan
|
|
|
148
159
|
# [:+instance_name+]
|
|
149
160
|
# The name of the instance variable for this resource.
|
|
150
161
|
#
|
|
162
|
+
# [:+through+]
|
|
163
|
+
# Authorize conditions on this parent resource when instance isn't available.
|
|
164
|
+
#
|
|
151
165
|
def authorize_resource(*args)
|
|
152
|
-
|
|
166
|
+
cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
|
|
170
|
+
# If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised.
|
|
171
|
+
# This is normally added to the ApplicationController to ensure all controller actions do authorization.
|
|
172
|
+
#
|
|
173
|
+
# class ApplicationController < ActionController::Base
|
|
174
|
+
# check_authorization
|
|
175
|
+
# end
|
|
176
|
+
#
|
|
177
|
+
# Any arguments are passed to the +after_filter+ it triggers.
|
|
178
|
+
#
|
|
179
|
+
# See skip_authorization_check to bypass this check on specific controller actions.
|
|
180
|
+
def check_authorization(*args)
|
|
181
|
+
self.after_filter(*args) do |controller|
|
|
182
|
+
unless controller.instance_variable_defined?(:@_authorized)
|
|
183
|
+
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
|
|
189
|
+
#
|
|
190
|
+
# class HomeController < ApplicationController
|
|
191
|
+
# skip_authorization_check :only => :index
|
|
192
|
+
# end
|
|
193
|
+
#
|
|
194
|
+
# Any arguments are passed to the +before_filter+ it triggers.
|
|
195
|
+
def skip_authorization_check(*args)
|
|
196
|
+
self.before_filter(*args) do |controller|
|
|
197
|
+
controller.instance_variable_set(:@_authorized, true)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def skip_authorization(*args)
|
|
202
|
+
raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def cancan_resource_class
|
|
206
|
+
if ancestors.map(&:to_s).include? "InheritedResources::Actions"
|
|
207
|
+
InheritedResource
|
|
208
|
+
else
|
|
209
|
+
ControllerResource
|
|
210
|
+
end
|
|
153
211
|
end
|
|
154
212
|
end
|
|
155
213
|
|
|
@@ -171,6 +229,16 @@ module CanCan
|
|
|
171
229
|
#
|
|
172
230
|
# authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
|
|
173
231
|
#
|
|
232
|
+
# You can also use I18n to customize the message. Action aliases defined in Ability work here.
|
|
233
|
+
#
|
|
234
|
+
# en:
|
|
235
|
+
# unauthorized:
|
|
236
|
+
# manage:
|
|
237
|
+
# all: "Not authorized to %{action} %{subject}."
|
|
238
|
+
# user: "Not allowed to manage other user accounts."
|
|
239
|
+
# update:
|
|
240
|
+
# project: "Not allowed to update this project."
|
|
241
|
+
#
|
|
174
242
|
# You can rescue from the exception in the controller to customize how unauthorized
|
|
175
243
|
# access is displayed to the user.
|
|
176
244
|
#
|
|
@@ -185,12 +253,9 @@ module CanCan
|
|
|
185
253
|
#
|
|
186
254
|
# See the load_and_authorize_resource method to automatically add the authorize! behavior
|
|
187
255
|
# to the default RESTful actions.
|
|
188
|
-
def authorize!(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
message = args.pop[:message]
|
|
192
|
-
end
|
|
193
|
-
raise AccessDenied.new(message, action, subject) if cannot?(action, subject, *args)
|
|
256
|
+
def authorize!(*args)
|
|
257
|
+
@_authorized = true
|
|
258
|
+
current_ability.authorize!(*args)
|
|
194
259
|
end
|
|
195
260
|
|
|
196
261
|
def unauthorized!(message = nil)
|
|
@@ -223,6 +288,13 @@ module CanCan
|
|
|
223
288
|
# <%= link_to "New Project", new_project_path %>
|
|
224
289
|
# <% end %>
|
|
225
290
|
#
|
|
291
|
+
# If it's a nested resource, you can pass the parent instance in a hash. This way it will
|
|
292
|
+
# check conditions which reach through that association.
|
|
293
|
+
#
|
|
294
|
+
# <% if can? :create, @category => Project %>
|
|
295
|
+
# <%= link_to "New Project", new_project_path %>
|
|
296
|
+
# <% end %>
|
|
297
|
+
#
|
|
226
298
|
# This simply calls "can?" on the current_ability. See Ability#can?.
|
|
227
299
|
def can?(*args)
|
|
228
300
|
current_ability.can?(*args)
|
|
@@ -6,7 +6,7 @@ module CanCan
|
|
|
6
6
|
options = args.extract_options!
|
|
7
7
|
resource_name = args.first
|
|
8
8
|
controller_class.before_filter(options.slice(:only, :except)) do |controller|
|
|
9
|
-
|
|
9
|
+
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -26,20 +26,22 @@ module CanCan
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def load_resource
|
|
29
|
-
if
|
|
30
|
-
|
|
29
|
+
if parent? || member_action?
|
|
30
|
+
self.resource_instance ||= load_resource_instance
|
|
31
|
+
elsif load_collection?
|
|
32
|
+
self.collection_instance ||= load_collection
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def authorize_resource
|
|
35
|
-
@controller.authorize!(authorization_action, resource_instance ||
|
|
37
|
+
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def parent?
|
|
39
41
|
@options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
protected
|
|
43
45
|
|
|
44
46
|
def load_resource_instance
|
|
45
47
|
if !parent? && new_actions.include?(@params[:action].to_sym)
|
|
@@ -49,13 +51,34 @@ module CanCan
|
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
|
|
54
|
+
def load_collection?
|
|
55
|
+
resource_base.respond_to?(:accessible_by) &&
|
|
56
|
+
!current_ability.has_block?(authorization_action, resource_class)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def load_collection
|
|
60
|
+
resource_base.accessible_by(current_ability)
|
|
61
|
+
end
|
|
62
|
+
|
|
52
63
|
def build_resource
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
if @options[:singleton] && resource_base.respond_to?("build_#{name}")
|
|
65
|
+
resource = resource_base.send("build_#{name}")
|
|
66
|
+
else
|
|
67
|
+
resource = resource_base.send("new")
|
|
68
|
+
end
|
|
69
|
+
initial_attributes.each do |name, value|
|
|
70
|
+
resource.send("#{name}=", value)
|
|
71
|
+
end
|
|
72
|
+
resource.attributes = @params[name] if @params[name]
|
|
73
|
+
resource
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def initial_attributes
|
|
77
|
+
current_ability.attributes_for(@params[:action].to_sym, resource_class)
|
|
55
78
|
end
|
|
56
79
|
|
|
57
80
|
def find_resource
|
|
58
|
-
if @options[:singleton]
|
|
81
|
+
if @options[:singleton] && resource_base.respond_to?(name)
|
|
59
82
|
resource_base.send(name)
|
|
60
83
|
else
|
|
61
84
|
@options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param)
|
|
@@ -86,24 +109,59 @@ module CanCan
|
|
|
86
109
|
end
|
|
87
110
|
end
|
|
88
111
|
|
|
112
|
+
def resource_class_with_parent
|
|
113
|
+
parent_resource ? {parent_resource => resource_class} : resource_class
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def resource_instance=(instance)
|
|
117
|
+
@controller.instance_variable_set("@#{instance_name}", instance)
|
|
118
|
+
end
|
|
119
|
+
|
|
89
120
|
def resource_instance
|
|
90
121
|
@controller.instance_variable_get("@#{instance_name}")
|
|
91
122
|
end
|
|
92
123
|
|
|
124
|
+
def collection_instance=(instance)
|
|
125
|
+
@controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def collection_instance
|
|
129
|
+
@controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
|
|
130
|
+
end
|
|
131
|
+
|
|
93
132
|
# The object that methods (such as "find", "new" or "build") are called on.
|
|
94
133
|
# If the :through option is passed it will go through an association on that instance.
|
|
134
|
+
# If the :shallow option is passed it will use the resource_class if there's no parent
|
|
95
135
|
# If the :singleton option is passed it won't use the association because it needs to be handled later.
|
|
96
136
|
def resource_base
|
|
97
|
-
if
|
|
98
|
-
|
|
137
|
+
if @options[:through]
|
|
138
|
+
if parent_resource
|
|
139
|
+
@options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
|
140
|
+
elsif @options[:shallow]
|
|
141
|
+
resource_class
|
|
142
|
+
else
|
|
143
|
+
raise AccessDenied # maybe this should be a record not found error instead?
|
|
144
|
+
end
|
|
99
145
|
else
|
|
100
146
|
resource_class
|
|
101
147
|
end
|
|
102
148
|
end
|
|
103
149
|
|
|
104
150
|
# The object to load this resource through.
|
|
105
|
-
def
|
|
106
|
-
@options[:through] && [@options[:through]].flatten.map { |i|
|
|
151
|
+
def parent_resource
|
|
152
|
+
@options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def fetch_parent(name)
|
|
156
|
+
if @controller.instance_variable_defined? "@#{name}"
|
|
157
|
+
@controller.instance_variable_get("@#{name}")
|
|
158
|
+
elsif @controller.respond_to? name
|
|
159
|
+
@controller.send(name)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def current_ability
|
|
164
|
+
@controller.send(:current_ability)
|
|
107
165
|
end
|
|
108
166
|
|
|
109
167
|
def name
|
data/lib/cancan/exceptions.rb
CHANGED
|
@@ -5,6 +5,9 @@ module CanCan
|
|
|
5
5
|
# Raised when removed code is called, an alternative solution is provided in message.
|
|
6
6
|
class ImplementationRemoved < Error; end
|
|
7
7
|
|
|
8
|
+
# Raised when using check_authorization without calling authorized!
|
|
9
|
+
class AuthorizationNotPerformed < Error; end
|
|
10
|
+
|
|
8
11
|
# This error is raised when a user isn't allowed to access a given controller action.
|
|
9
12
|
# This usually happens within a call to ControllerAdditions#authorize! but can be
|
|
10
13
|
# raised manually.
|
|
@@ -24,7 +27,8 @@ module CanCan
|
|
|
24
27
|
# exception.default_message = "Default error message"
|
|
25
28
|
# exception.message # => "Default error message"
|
|
26
29
|
#
|
|
27
|
-
# See ControllerAdditions#authorized! for more information on rescuing from this exception
|
|
30
|
+
# See ControllerAdditions#authorized! for more information on rescuing from this exception
|
|
31
|
+
# and customizing the message using I18n.
|
|
28
32
|
class AccessDenied < Error
|
|
29
33
|
attr_reader :action, :subject
|
|
30
34
|
attr_writer :default_message
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module CanCan
|
|
2
|
+
# For use with Inherited Resources
|
|
3
|
+
class InheritedResource < ControllerResource # :nodoc:
|
|
4
|
+
def load_resource_instance
|
|
5
|
+
if parent?
|
|
6
|
+
@controller.send :parent
|
|
7
|
+
elsif new_actions.include? @params[:action].to_sym
|
|
8
|
+
@controller.send :build_resource
|
|
9
|
+
else
|
|
10
|
+
@controller.send :resource
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def resource_base
|
|
15
|
+
@controller.send :end_of_association_chain
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/cancan.rb
CHANGED
data/spec/cancan/ability_spec.rb
CHANGED
|
@@ -24,6 +24,14 @@ describe CanCan::Ability do
|
|
|
24
24
|
@ability.can?(:read, :some_symbol).should == true
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
it "should pass nil to a block when no instance is passed" do
|
|
28
|
+
@ability.can :read, Symbol do |sym|
|
|
29
|
+
sym.should be_nil
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
@ability.can?(:read, Symbol).should be_true
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
it "should pass to previous can definition, if block returns false or nil" do
|
|
28
36
|
@ability.can :read, Symbol
|
|
29
37
|
@ability.can :read, Integer do |i|
|
|
@@ -38,9 +46,8 @@ describe CanCan::Ability do
|
|
|
38
46
|
@ability.can?(:read, 6).should be_false
|
|
39
47
|
end
|
|
40
48
|
|
|
41
|
-
it "should pass class with object if :all objects are accepted" do
|
|
42
|
-
@ability.can :preview, :all do |
|
|
43
|
-
object_class.should == Fixnum
|
|
49
|
+
it "should not pass class with object if :all objects are accepted" do
|
|
50
|
+
@ability.can :preview, :all do |object|
|
|
44
51
|
object.should == 123
|
|
45
52
|
@block_called = true
|
|
46
53
|
end
|
|
@@ -48,23 +55,21 @@ describe CanCan::Ability do
|
|
|
48
55
|
@block_called.should be_true
|
|
49
56
|
end
|
|
50
57
|
|
|
51
|
-
it "should
|
|
52
|
-
@
|
|
53
|
-
|
|
54
|
-
object.should be_nil
|
|
58
|
+
it "should not call block when only class is passed, only return true" do
|
|
59
|
+
@block_called = false
|
|
60
|
+
@ability.can :preview, :all do |object|
|
|
55
61
|
@block_called = true
|
|
56
62
|
end
|
|
57
|
-
@ability.can?(:preview, Hash)
|
|
58
|
-
@block_called.should
|
|
63
|
+
@ability.can?(:preview, Hash).should be_true
|
|
64
|
+
@block_called.should be_false
|
|
59
65
|
end
|
|
60
66
|
|
|
61
|
-
it "should pass
|
|
62
|
-
@ability.can :manage,
|
|
63
|
-
|
|
64
|
-
object.should == [1, 2]
|
|
67
|
+
it "should pass only object for global manage actions" do
|
|
68
|
+
@ability.can :manage, String do |object|
|
|
69
|
+
object.should == "foo"
|
|
65
70
|
@block_called = true
|
|
66
71
|
end
|
|
67
|
-
@ability.can?(:stuff,
|
|
72
|
+
@ability.can?(:stuff, "foo").should
|
|
68
73
|
@block_called.should be_true
|
|
69
74
|
end
|
|
70
75
|
|
|
@@ -82,10 +87,10 @@ describe CanCan::Ability do
|
|
|
82
87
|
@ability.can?(:increment, 123).should be_true
|
|
83
88
|
end
|
|
84
89
|
|
|
85
|
-
it "should
|
|
86
|
-
@ability.can
|
|
90
|
+
it "should always call block with arguments when passing no arguments to can" do
|
|
91
|
+
@ability.can do |action, object_class, object|
|
|
87
92
|
action.should == :foo
|
|
88
|
-
object_class.should ==
|
|
93
|
+
object_class.should == 123.class
|
|
89
94
|
object.should == 123
|
|
90
95
|
@block_called = true
|
|
91
96
|
end
|
|
@@ -93,6 +98,17 @@ describe CanCan::Ability do
|
|
|
93
98
|
@block_called.should be_true
|
|
94
99
|
end
|
|
95
100
|
|
|
101
|
+
it "should pass nil to object when comparing class with can check" do
|
|
102
|
+
@ability.can do |action, object_class, object|
|
|
103
|
+
action.should == :foo
|
|
104
|
+
object_class.should == Hash
|
|
105
|
+
object.should be_nil
|
|
106
|
+
@block_called = true
|
|
107
|
+
end
|
|
108
|
+
@ability.can?(:foo, Hash)
|
|
109
|
+
@block_called.should be_true
|
|
110
|
+
end
|
|
111
|
+
|
|
96
112
|
it "should automatically alias index and show into read calls" do
|
|
97
113
|
@ability.can :read, :all
|
|
98
114
|
@ability.can?(:index, 123).should be_true
|
|
@@ -122,9 +138,9 @@ describe CanCan::Ability do
|
|
|
122
138
|
end
|
|
123
139
|
|
|
124
140
|
it "should be able to specify multiple classes and match any" do
|
|
125
|
-
@ability.can :update, [String,
|
|
141
|
+
@ability.can :update, [String, Range]
|
|
126
142
|
@ability.can?(:update, "foo").should be_true
|
|
127
|
-
@ability.can?(:update,
|
|
143
|
+
@ability.can?(:update, 1..3).should be_true
|
|
128
144
|
@ability.can?(:update, 123).should be_false
|
|
129
145
|
end
|
|
130
146
|
|
|
@@ -149,18 +165,7 @@ describe CanCan::Ability do
|
|
|
149
165
|
@ability.can?(:read, 123).should be_false
|
|
150
166
|
end
|
|
151
167
|
|
|
152
|
-
it "should support block on 'cannot' method" do
|
|
153
|
-
@ability.can :read, :all
|
|
154
|
-
@ability.cannot :read, Integer do |int|
|
|
155
|
-
int > 5
|
|
156
|
-
end
|
|
157
|
-
@ability.can?(:read, "foo").should be_true
|
|
158
|
-
@ability.can?(:read, 3).should be_true
|
|
159
|
-
@ability.can?(:read, 123).should be_false
|
|
160
|
-
end
|
|
161
|
-
|
|
162
168
|
it "should pass to previous can definition, if block returns false or nil" do
|
|
163
|
-
#same as previous
|
|
164
169
|
@ability.can :read, :all
|
|
165
170
|
@ability.cannot :read, Integer do |int|
|
|
166
171
|
int > 10 ? nil : ( int > 5 )
|
|
@@ -169,7 +174,6 @@ describe CanCan::Ability do
|
|
|
169
174
|
@ability.can?(:read, 3).should be_true
|
|
170
175
|
@ability.can?(:read, 8).should be_false
|
|
171
176
|
@ability.can?(:read, 123).should be_true
|
|
172
|
-
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
it "should always return `false` for single cannot definition" do
|
|
@@ -214,49 +218,170 @@ describe CanCan::Ability do
|
|
|
214
218
|
end
|
|
215
219
|
|
|
216
220
|
it "should use conditions as third parameter and determine abilities from it" do
|
|
217
|
-
@ability.can :read,
|
|
218
|
-
@ability.can?(:read,
|
|
219
|
-
@ability.can?(:read,
|
|
220
|
-
@ability.can?(:read,
|
|
221
|
+
@ability.can :read, Range, :begin => 1, :end => 3
|
|
222
|
+
@ability.can?(:read, 1..3).should be_true
|
|
223
|
+
@ability.can?(:read, 1..4).should be_false
|
|
224
|
+
@ability.can?(:read, Range).should be_true
|
|
221
225
|
end
|
|
222
226
|
|
|
223
227
|
it "should allow an array of options in conditions hash" do
|
|
224
|
-
@ability.can :read,
|
|
225
|
-
@ability.can?(:read,
|
|
226
|
-
@ability.can?(:read,
|
|
227
|
-
@ability.can?(:read,
|
|
228
|
+
@ability.can :read, Range, :begin => [1, 3, 5]
|
|
229
|
+
@ability.can?(:read, 1..3).should be_true
|
|
230
|
+
@ability.can?(:read, 2..4).should be_false
|
|
231
|
+
@ability.can?(:read, 3..5).should be_true
|
|
228
232
|
end
|
|
229
233
|
|
|
230
234
|
it "should allow a range of options in conditions hash" do
|
|
231
|
-
@ability.can :read,
|
|
232
|
-
@ability.can?(:read,
|
|
233
|
-
@ability.can?(:read,
|
|
234
|
-
@ability.can?(:read,
|
|
235
|
+
@ability.can :read, Range, :begin => 1..3
|
|
236
|
+
@ability.can?(:read, 1..10).should be_true
|
|
237
|
+
@ability.can?(:read, 3..30).should be_true
|
|
238
|
+
@ability.can?(:read, 4..40).should be_false
|
|
235
239
|
end
|
|
236
240
|
|
|
237
241
|
it "should allow nested hashes in conditions hash" do
|
|
238
|
-
@ability.can :read,
|
|
239
|
-
@ability.can?(:read,
|
|
240
|
-
@ability.can?(:read,
|
|
242
|
+
@ability.can :read, Range, :begin => { :to_i => 5 }
|
|
243
|
+
@ability.can?(:read, 5..7).should be_true
|
|
244
|
+
@ability.can?(:read, 6..8).should be_false
|
|
241
245
|
end
|
|
242
246
|
|
|
243
|
-
it "should
|
|
244
|
-
@ability.can :read,
|
|
245
|
-
@ability.can?(:read,
|
|
246
|
-
@ability.can?(:read,
|
|
247
|
+
it "should match any element passed in to nesting if it's an array (for has_many associations)" do
|
|
248
|
+
@ability.can :read, Range, :to_a => { :to_i => 3 }
|
|
249
|
+
@ability.can?(:read, 1..5).should be_true
|
|
250
|
+
@ability.can?(:read, 4..6).should be_false
|
|
247
251
|
end
|
|
248
252
|
|
|
249
253
|
it "should not stop at cannot definition when comparing class" do
|
|
250
|
-
@ability.can :read,
|
|
251
|
-
@ability.cannot :read,
|
|
252
|
-
@ability.can?(:read,
|
|
253
|
-
@ability.can?(:read,
|
|
254
|
-
@ability.can?(:read,
|
|
254
|
+
@ability.can :read, Range
|
|
255
|
+
@ability.cannot :read, Range, :begin => 1
|
|
256
|
+
@ability.can?(:read, 2..5).should be_true
|
|
257
|
+
@ability.can?(:read, 1..5).should be_false
|
|
258
|
+
@ability.can?(:read, Range).should be_true
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "should stop at cannot definition when no hash is present" do
|
|
262
|
+
@ability.can :read, :all
|
|
263
|
+
@ability.cannot :read, Range
|
|
264
|
+
@ability.can?(:read, 1..5).should be_false
|
|
265
|
+
@ability.can?(:read, Range).should be_false
|
|
255
266
|
end
|
|
256
267
|
|
|
257
|
-
it "should
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
it "should allow to check ability for Module" do
|
|
269
|
+
module B; end
|
|
270
|
+
class A; include B; end
|
|
271
|
+
@ability.can :read, B
|
|
272
|
+
@ability.can?(:read, A).should be_true
|
|
273
|
+
@ability.can?(:read, A.new).should be_true
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "should pass nil to a block for ability on Module when no instance is passed" do
|
|
277
|
+
module B; end
|
|
278
|
+
class A; include B; end
|
|
279
|
+
@ability.can :read, B do |sym|
|
|
280
|
+
sym.should be_nil
|
|
281
|
+
true
|
|
282
|
+
end
|
|
283
|
+
@ability.can?(:read, B).should be_true
|
|
284
|
+
@ability.can?(:read, A).should be_true
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "passing a hash of subjects should check permissions through association" do
|
|
288
|
+
@ability.can :read, Range, :string => {:length => 3}
|
|
289
|
+
@ability.can?(:read, "foo" => Range).should be_true
|
|
290
|
+
@ability.can?(:read, "foobar" => Range).should be_false
|
|
291
|
+
@ability.can?(:read, 123 => Range).should be_true
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
it "should have initial attributes based on hash conditions of 'new' action" do
|
|
295
|
+
@ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
|
|
296
|
+
@ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
|
|
297
|
+
@ability.can :new, Range, :baz => "baz", :range => 1..3
|
|
298
|
+
@ability.cannot :new, Range, :ignore => "me"
|
|
299
|
+
@ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "should raise access denied exception if ability us unauthorized to perform a certain action" do
|
|
303
|
+
begin
|
|
304
|
+
@ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
|
|
305
|
+
rescue CanCan::AccessDenied => e
|
|
306
|
+
e.message.should == "Access denied!"
|
|
307
|
+
e.action.should == :read
|
|
308
|
+
e.subject.should == :foo
|
|
309
|
+
else
|
|
310
|
+
fail "Expected CanCan::AccessDenied exception to be raised"
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "should not raise access denied exception if ability is authorized to perform an action" do
|
|
315
|
+
@ability.can :read, :foo
|
|
316
|
+
lambda { @ability.authorize!(:read, :foo) }.should_not raise_error
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "should know when block is used in conditions" do
|
|
320
|
+
@ability.can :read, :foo
|
|
321
|
+
@ability.should_not have_block(:read, :foo)
|
|
322
|
+
@ability.can :read, :foo do |foo|
|
|
323
|
+
false
|
|
324
|
+
end
|
|
325
|
+
@ability.should have_block(:read, :foo)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it "should know when raw sql is used in conditions" do
|
|
329
|
+
@ability.can :read, :foo
|
|
330
|
+
@ability.should_not have_raw_sql(:read, :foo)
|
|
331
|
+
@ability.can :read, :foo, 'false'
|
|
332
|
+
@ability.should have_raw_sql(:read, :foo)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it "should raise access denied exception with default message if not specified" do
|
|
336
|
+
begin
|
|
337
|
+
@ability.authorize! :read, :foo
|
|
338
|
+
rescue CanCan::AccessDenied => e
|
|
339
|
+
e.default_message = "Access denied!"
|
|
340
|
+
e.message.should == "Access denied!"
|
|
341
|
+
else
|
|
342
|
+
fail "Expected CanCan::AccessDenied exception to be raised"
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
describe "unauthorized message" do
|
|
347
|
+
after(:each) do
|
|
348
|
+
I18n.backend = nil
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "should use action/subject in i18n" do
|
|
352
|
+
I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
|
|
353
|
+
@ability.unauthorized_message(:update, Array).should == "foo"
|
|
354
|
+
@ability.unauthorized_message(:update, [1, 2, 3]).should == "foo"
|
|
355
|
+
@ability.unauthorized_message(:update, :missing).should be_nil
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it "should use symbol as subject directly" do
|
|
359
|
+
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
|
|
360
|
+
@ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it "should fall back to 'manage' and 'all'" do
|
|
364
|
+
I18n.backend.store_translations :en, :unauthorized => {
|
|
365
|
+
:manage => {:all => "manage all", :array => "manage array"},
|
|
366
|
+
:update => {:all => "update all", :array => "update array"}
|
|
367
|
+
}
|
|
368
|
+
@ability.unauthorized_message(:update, Array).should == "update array"
|
|
369
|
+
@ability.unauthorized_message(:update, Hash).should == "update all"
|
|
370
|
+
@ability.unauthorized_message(:foo, Array).should == "manage array"
|
|
371
|
+
@ability.unauthorized_message(:foo, Hash).should == "manage all"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it "should follow aliased actions" do
|
|
375
|
+
I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
|
|
376
|
+
@ability.alias_action :update, :to => :modify
|
|
377
|
+
@ability.unauthorized_message(:update, Array).should == "modify array"
|
|
378
|
+
@ability.unauthorized_message(:edit, Array).should == "modify array"
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it "should have variables for action and subject" do
|
|
382
|
+
I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
|
383
|
+
@ability.unauthorized_message(:update, Array).should == "update array"
|
|
384
|
+
@ability.unauthorized_message(:edit, 1..3).should == "edit range"
|
|
385
|
+
end
|
|
261
386
|
end
|
|
262
387
|
end
|