cancan 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +21 -0
- data/Gemfile +2 -1
- data/README.rdoc +13 -3
- data/lib/cancan/ability.rb +1 -1
- data/lib/cancan/controller_additions.rb +31 -9
- data/lib/cancan/controller_resource.rb +4 -3
- data/lib/cancan/inherited_resource.rb +3 -2
- data/lib/cancan/model_adapters/abstract_adapter.rb +11 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +47 -2
- data/lib/cancan/rule.rb +17 -12
- data/spec/cancan/ability_spec.rb +15 -0
- data/spec/cancan/controller_additions_spec.rb +25 -4
- data/spec/cancan/controller_resource_spec.rb +16 -7
- data/spec/cancan/inherited_resource_spec.rb +6 -6
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +60 -0
- metadata +7 -7
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
1.6.0 (March 11, 2011)
|
2
|
+
|
3
|
+
* Added MetaWhere support - see issue #194 and #261
|
4
|
+
|
5
|
+
* Allow Active Record scopes in Ability conditions - see issue #257
|
6
|
+
|
7
|
+
* Added :if and :unless options to check_authorization - see issue #284
|
8
|
+
|
9
|
+
* Several Inherited Resources fixes (thanks aq1018, tanordheim and stefanoverna)
|
10
|
+
|
11
|
+
* Pass action name to accessible_by call when loading a collection (thanks amw)
|
12
|
+
|
13
|
+
* Added :prepend option to load_and_authorize_resource to load before other filters - see issue #290
|
14
|
+
|
15
|
+
* Fixed spacing issue in I18n message for multi-word model names - see issue #292
|
16
|
+
|
17
|
+
* Load resource collection for any action which doesn't have an "id" parameter - see issue #296
|
18
|
+
|
19
|
+
* Raise an exception when trying to make a Ability condition with both a hash of conditions and a block - see issue #269
|
20
|
+
|
21
|
+
|
1
22
|
1.5.1 (January 20, 2011)
|
2
23
|
|
3
24
|
* Fixing deeply nested conditions in Active Record adapter - see issue #246
|
data/Gemfile
CHANGED
@@ -2,9 +2,10 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
case ENV["MODEL_ADAPTER"]
|
4
4
|
when nil, "active_record"
|
5
|
-
gem "sqlite3
|
5
|
+
gem "sqlite3"
|
6
6
|
gem "activerecord", :require => "active_record"
|
7
7
|
gem "with_model"
|
8
|
+
gem "meta_where"
|
8
9
|
when "data_mapper"
|
9
10
|
gem "dm-core", "~> 1.0.2"
|
10
11
|
gem "dm-sqlite-adapter", "~> 1.0.2"
|
data/README.rdoc
CHANGED
@@ -70,17 +70,27 @@ If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will
|
|
70
70
|
|
71
71
|
class ApplicationController < ActionController::Base
|
72
72
|
rescue_from CanCan::AccessDenied do |exception|
|
73
|
-
|
74
|
-
redirect_to root_url
|
73
|
+
redirect_to root_url, :alert => exception.message
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
78
77
|
See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
|
79
78
|
|
80
79
|
|
80
|
+
=== 4. Lock It Down
|
81
|
+
|
82
|
+
If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
|
83
|
+
|
84
|
+
class ApplicationController < ActionController::Base
|
85
|
+
check_authorization
|
86
|
+
end
|
87
|
+
|
88
|
+
This will raise an exception if authorization is not performed in an action. If you want to skip this add +skip_authorization_check+ to a controller subclass. See {Ensure Authorization}[https://github.com/ryanb/cancan/wiki/Ensure-Authorization] for more information.
|
89
|
+
|
90
|
+
|
81
91
|
== Wiki Docs
|
82
92
|
|
83
|
-
* {Upgrading to 1.
|
93
|
+
* {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
|
84
94
|
* {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
|
85
95
|
* {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
|
86
96
|
* {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
|
data/lib/cancan/ability.rb
CHANGED
@@ -206,7 +206,7 @@ module CanCan
|
|
206
206
|
def unauthorized_message(action, subject)
|
207
207
|
keys = unauthorized_message_keys(action, subject)
|
208
208
|
variables = {:action => action.to_s}
|
209
|
-
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
|
209
|
+
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
210
210
|
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
211
211
|
message.blank? ? nil : message
|
212
212
|
end
|
@@ -109,6 +109,9 @@ module CanCan
|
|
109
109
|
#
|
110
110
|
# load_resource :new => :build
|
111
111
|
#
|
112
|
+
# [:+prepend+]
|
113
|
+
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
114
|
+
#
|
112
115
|
def load_resource(*args)
|
113
116
|
cancan_resource_class.add_before_filter(self, :load_resource, *args)
|
114
117
|
end
|
@@ -162,6 +165,9 @@ module CanCan
|
|
162
165
|
# [:+through+]
|
163
166
|
# Authorize conditions on this parent resource when instance isn't available.
|
164
167
|
#
|
168
|
+
# [:+prepend+]
|
169
|
+
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
170
|
+
#
|
165
171
|
def authorize_resource(*args)
|
166
172
|
cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
|
167
173
|
end
|
@@ -220,14 +226,31 @@ module CanCan
|
|
220
226
|
# check_authorization
|
221
227
|
# end
|
222
228
|
#
|
223
|
-
# Any arguments are passed to the +after_filter+ it triggers.
|
224
|
-
#
|
225
229
|
# See skip_authorization_check to bypass this check on specific controller actions.
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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."
|
231
254
|
end
|
232
255
|
end
|
233
256
|
|
@@ -294,8 +317,7 @@ module CanCan
|
|
294
317
|
#
|
295
318
|
# class ApplicationController < ActionController::Base
|
296
319
|
# rescue_from CanCan::AccessDenied do |exception|
|
297
|
-
#
|
298
|
-
# redirect_to root_url
|
320
|
+
# redirect_to root_url, :alert => exception.message
|
299
321
|
# end
|
300
322
|
# end
|
301
323
|
#
|
@@ -5,7 +5,8 @@ module CanCan
|
|
5
5
|
def self.add_before_filter(controller_class, method, *args)
|
6
6
|
options = args.extract_options!
|
7
7
|
resource_name = args.first
|
8
|
-
|
8
|
+
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
|
9
|
+
controller_class.send(before_filter_method, options.slice(:only, :except)) do |controller|
|
9
10
|
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
|
10
11
|
end
|
11
12
|
end
|
@@ -77,7 +78,7 @@ module CanCan
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def load_collection
|
80
|
-
resource_base.accessible_by(current_ability)
|
81
|
+
resource_base.accessible_by(current_ability, authorization_action)
|
81
82
|
end
|
82
83
|
|
83
84
|
def build_resource
|
@@ -112,7 +113,7 @@ module CanCan
|
|
112
113
|
end
|
113
114
|
|
114
115
|
def member_action?
|
115
|
-
!collection_actions.include?
|
116
|
+
new_actions.include?(@params[:action].to_sym) || (@params[:id] && !collection_actions.include?(@params[:action].to_sym))
|
116
117
|
end
|
117
118
|
|
118
119
|
# Returns the class used for this resource. This can be overriden by the :class option.
|
@@ -3,7 +3,8 @@ module CanCan
|
|
3
3
|
class InheritedResource < ControllerResource # :nodoc:
|
4
4
|
def load_resource_instance
|
5
5
|
if parent?
|
6
|
-
@controller.send :
|
6
|
+
@controller.send :association_chain
|
7
|
+
@controller.instance_variable_get("@#{instance_name}")
|
7
8
|
elsif new_actions.include? @params[:action].to_sym
|
8
9
|
@controller.send :build_resource
|
9
10
|
else
|
@@ -12,7 +13,7 @@ module CanCan
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def resource_base
|
15
|
-
@controller.send :
|
16
|
+
@controller.send :collection
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -26,6 +26,17 @@ module CanCan
|
|
26
26
|
raise NotImplemented, "This model adapter does not support matching on a conditions hash."
|
27
27
|
end
|
28
28
|
|
29
|
+
# Used to determine if this model adapter will override the matching behavior for a specific condition.
|
30
|
+
# If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
|
31
|
+
def self.override_condition_matching?(subject, name, value)
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Override if override_condition_matching? returns true
|
36
|
+
def self.matches_condition?(subject, name, value)
|
37
|
+
raise NotImplemented, "This model adapter does not support matching on a specific condition."
|
38
|
+
end
|
39
|
+
|
29
40
|
def initialize(model_class, rules)
|
30
41
|
@model_class = model_class
|
31
42
|
@rules = rules
|
@@ -5,6 +5,37 @@ module CanCan
|
|
5
5
|
model_class <= ActiveRecord::Base
|
6
6
|
end
|
7
7
|
|
8
|
+
def self.override_condition_matching?(subject, name, value)
|
9
|
+
name.kind_of?(MetaWhere::Column) if defined? MetaWhere
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.matches_condition?(subject, name, value)
|
13
|
+
subject_value = subject.send(name.column)
|
14
|
+
if name.method.to_s.ends_with? "_any"
|
15
|
+
value.any? { |v| meta_where_match? subject_value, name.method.to_s.sub("_any", ""), v }
|
16
|
+
elsif name.method.to_s.ends_with? "_all"
|
17
|
+
value.all? { |v| meta_where_match? subject_value, name.method.to_s.sub("_all", ""), v }
|
18
|
+
else
|
19
|
+
meta_where_match? subject_value, name.method, value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.meta_where_match?(subject_value, method, value)
|
24
|
+
case method.to_sym
|
25
|
+
when :eq then subject_value == value
|
26
|
+
when :not_eq then subject_value != value
|
27
|
+
when :in then value.include?(subject_value)
|
28
|
+
when :not_in then !value.include?(subject_value)
|
29
|
+
when :lt then subject_value < value
|
30
|
+
when :lteq then subject_value <= value
|
31
|
+
when :gt then subject_value > value
|
32
|
+
when :gteq then subject_value >= value
|
33
|
+
when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true)
|
34
|
+
when :does_not_match then !meta_where_match?(subject_value, :matches, value)
|
35
|
+
else raise NotImplemented, "The #{method} MetaWhere condition is not supported."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
8
39
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
9
40
|
# method directly, but instead go through ModelAdditions#accessible_by.
|
10
41
|
#
|
@@ -36,7 +67,7 @@ module CanCan
|
|
36
67
|
conditions.inject({}) do |result_hash, (name, value)|
|
37
68
|
if value.kind_of? Hash
|
38
69
|
association_class = model_class.reflect_on_association(name).class_name.constantize
|
39
|
-
name = model_class.reflect_on_association(name).table_name
|
70
|
+
name = model_class.reflect_on_association(name).table_name.to_sym
|
40
71
|
value = tableized_conditions(value, association_class)
|
41
72
|
end
|
42
73
|
result_hash[name] = value
|
@@ -55,7 +86,9 @@ module CanCan
|
|
55
86
|
end
|
56
87
|
|
57
88
|
def database_records
|
58
|
-
if
|
89
|
+
if override_scope
|
90
|
+
override_scope
|
91
|
+
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
59
92
|
@model_class.where(conditions).joins(joins)
|
60
93
|
else
|
61
94
|
@model_class.scoped(:conditions => conditions, :joins => joins)
|
@@ -64,6 +97,18 @@ module CanCan
|
|
64
97
|
|
65
98
|
private
|
66
99
|
|
100
|
+
def override_scope
|
101
|
+
conditions = @rules.map(&:conditions).compact
|
102
|
+
if conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
|
103
|
+
if conditions.size == 1
|
104
|
+
conditions.first
|
105
|
+
else
|
106
|
+
rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
|
107
|
+
raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
67
112
|
def merge_conditions(sql, conditions_hash, behavior)
|
68
113
|
if conditions_hash.blank?
|
69
114
|
behavior ? true_sql : false_sql
|
data/lib/cancan/rule.rb
CHANGED
@@ -3,7 +3,7 @@ module CanCan
|
|
3
3
|
# it holds the information about a "can" call made on Ability and provides
|
4
4
|
# helpful methods to determine permission checking and conditions hash generation.
|
5
5
|
class Rule # :nodoc:
|
6
|
-
attr_reader :base_behavior, :actions, :conditions
|
6
|
+
attr_reader :base_behavior, :subjects, :actions, :conditions
|
7
7
|
attr_writer :expanded_actions
|
8
8
|
|
9
9
|
# The first argument when initializing is the base_behavior which is a true/false
|
@@ -11,6 +11,7 @@ module CanCan
|
|
11
11
|
# and subject respectively (such as :read, @project). The third argument is a hash
|
12
12
|
# of conditions and the last one is the block passed to the "can" call.
|
13
13
|
def initialize(base_behavior, action, subject, conditions, block)
|
14
|
+
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
|
14
15
|
@match_all = action.nil? && subject.nil?
|
15
16
|
@base_behavior = base_behavior
|
16
17
|
@actions = [action].flatten
|
@@ -21,7 +22,7 @@ module CanCan
|
|
21
22
|
|
22
23
|
# Matches both the subject and action, not necessarily the conditions
|
23
24
|
def relevant?(action, subject)
|
24
|
-
subject = subject.values.first if subject.
|
25
|
+
subject = subject.values.first if subject.class == Hash
|
25
26
|
@match_all || (matches_action?(action) && matches_subject?(subject))
|
26
27
|
end
|
27
28
|
|
@@ -31,7 +32,7 @@ module CanCan
|
|
31
32
|
call_block_with_all(action, subject, extra_args)
|
32
33
|
elsif @block && !subject_class?(subject)
|
33
34
|
@block.call(subject, *extra_args)
|
34
|
-
elsif @conditions.kind_of?(Hash) && subject.
|
35
|
+
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
35
36
|
nested_subject_matches_conditions?(subject)
|
36
37
|
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
37
38
|
matches_conditions_hash?(subject)
|
@@ -100,17 +101,21 @@ module CanCan
|
|
100
101
|
model_adapter(subject).matches_conditions_hash? subject, conditions
|
101
102
|
else
|
102
103
|
conditions.all? do |name, value|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
if model_adapter(subject).override_condition_matching? subject, name, value
|
105
|
+
model_adapter(subject).matches_condition? subject, name, value
|
106
|
+
else
|
107
|
+
attribute = subject.send(name)
|
108
|
+
if value.kind_of?(Hash)
|
109
|
+
if attribute.kind_of? Array
|
110
|
+
attribute.any? { |element| matches_conditions_hash? element, value }
|
111
|
+
else
|
112
|
+
matches_conditions_hash? attribute, value
|
113
|
+
end
|
114
|
+
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
115
|
+
value.include? attribute
|
107
116
|
else
|
108
|
-
|
117
|
+
attribute == value
|
109
118
|
end
|
110
|
-
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
111
|
-
value.include? attribute
|
112
|
-
else
|
113
|
-
attribute == value
|
114
119
|
end
|
115
120
|
end
|
116
121
|
end
|
data/spec/cancan/ability_spec.rb
CHANGED
@@ -290,6 +290,12 @@ describe CanCan::Ability do
|
|
290
290
|
@ability.can?(:read, "foobar" => Range).should be_false
|
291
291
|
@ability.can?(:read, 123 => Range).should be_true
|
292
292
|
end
|
293
|
+
|
294
|
+
it "should allow to check ability on Hash-like object" do
|
295
|
+
class Container < Hash; end
|
296
|
+
@ability.can :read, Container
|
297
|
+
@ability.can?(:read, Container.new).should be_true
|
298
|
+
end
|
293
299
|
|
294
300
|
it "should have initial attributes based on hash conditions of 'new' action" do
|
295
301
|
@ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
|
@@ -351,6 +357,14 @@ describe CanCan::Ability do
|
|
351
357
|
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
352
358
|
end
|
353
359
|
|
360
|
+
it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
361
|
+
lambda {
|
362
|
+
@ability.can :read, Array, :published => true do
|
363
|
+
false
|
364
|
+
end
|
365
|
+
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
|
366
|
+
end
|
367
|
+
|
354
368
|
describe "unauthorized message" do
|
355
369
|
after(:each) do
|
356
370
|
I18n.backend = nil
|
@@ -389,6 +403,7 @@ describe CanCan::Ability do
|
|
389
403
|
it "should have variables for action and subject" do
|
390
404
|
I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
391
405
|
@ability.unauthorized_message(:update, Array).should == "update array"
|
406
|
+
@ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
392
407
|
@ability.unauthorized_message(:edit, 1..3).should == "edit range"
|
393
408
|
end
|
394
409
|
end
|
@@ -42,6 +42,11 @@ describe CanCan::ControllerAdditions do
|
|
42
42
|
@controller_class.load_and_authorize_resource :project, :foo => :bar
|
43
43
|
end
|
44
44
|
|
45
|
+
it "load_and_authorize_resource with :prepend should prepend the before filter" do
|
46
|
+
mock(@controller_class).prepend_before_filter({})
|
47
|
+
@controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
|
48
|
+
end
|
49
|
+
|
45
50
|
it "authorize_resource should setup a before filter which passes call to ControllerResource" do
|
46
51
|
stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
|
47
52
|
mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
|
@@ -61,17 +66,33 @@ describe CanCan::ControllerAdditions do
|
|
61
66
|
end
|
62
67
|
|
63
68
|
it "check_authorization should trigger AuthorizationNotPerformed in after filter" do
|
64
|
-
mock(@controller_class).after_filter(:
|
69
|
+
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
65
70
|
lambda {
|
66
|
-
@controller_class.check_authorization(:
|
71
|
+
@controller_class.check_authorization(:only => [:test])
|
67
72
|
}.should raise_error(CanCan::AuthorizationNotPerformed)
|
68
73
|
end
|
69
74
|
|
75
|
+
it "check_authorization should not trigger AuthorizationNotPerformed when :if is false" do
|
76
|
+
stub(@controller).check_auth? { false }
|
77
|
+
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
78
|
+
lambda {
|
79
|
+
@controller_class.check_authorization(:if => :check_auth?)
|
80
|
+
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "check_authorization should not trigger AuthorizationNotPerformed when :unless is true" do
|
84
|
+
stub(@controller).engine_controller? { true }
|
85
|
+
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
86
|
+
lambda {
|
87
|
+
@controller_class.check_authorization(:unless => :engine_controller?)
|
88
|
+
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
89
|
+
end
|
90
|
+
|
70
91
|
it "check_authorization should not raise error when @_authorized is set" do
|
71
92
|
@controller.instance_variable_set(:@_authorized, true)
|
72
|
-
mock(@controller_class).after_filter(:
|
93
|
+
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
73
94
|
lambda {
|
74
|
-
@controller_class.check_authorization(:
|
95
|
+
@controller_class.check_authorization(:only => [:test])
|
75
96
|
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
76
97
|
end
|
77
98
|
|
@@ -67,7 +67,7 @@ describe CanCan::ControllerResource do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
it "should build a collection when on index action when class responds to accessible_by" do
|
70
|
-
stub(Project).accessible_by(@ability) { :found_projects }
|
70
|
+
stub(Project).accessible_by(@ability, :index) { :found_projects }
|
71
71
|
@params[:action] = "index"
|
72
72
|
resource = CanCan::ControllerResource.new(@controller, :project)
|
73
73
|
resource.load_resource
|
@@ -110,7 +110,7 @@ describe CanCan::ControllerResource do
|
|
110
110
|
end
|
111
111
|
|
112
112
|
it "should perform authorization using controller action and loaded model" do
|
113
|
-
@params
|
113
|
+
@params.merge!(:action => "show", :id => 123)
|
114
114
|
@controller.instance_variable_set(:@project, :some_project)
|
115
115
|
stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
|
116
116
|
resource = CanCan::ControllerResource.new(@controller)
|
@@ -118,27 +118,36 @@ describe CanCan::ControllerResource do
|
|
118
118
|
end
|
119
119
|
|
120
120
|
it "should perform authorization using controller action and non loaded model" do
|
121
|
-
@params
|
121
|
+
@params.merge!(:action => "show", :id => 123)
|
122
122
|
stub(@controller).authorize!(:show, Project) { raise CanCan::AccessDenied }
|
123
123
|
resource = CanCan::ControllerResource.new(@controller)
|
124
124
|
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
125
125
|
end
|
126
126
|
|
127
127
|
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
|
128
|
-
@params
|
128
|
+
@params.merge!(:action => "show", :id => 123)
|
129
129
|
resource = CanCan::ControllerResource.new(@controller)
|
130
130
|
mock(resource).load_resource
|
131
131
|
mock(resource).authorize_resource
|
132
132
|
resource.load_and_authorize_resource
|
133
133
|
end
|
134
134
|
|
135
|
-
it "should not build a resource when on custom collection action" do
|
136
|
-
@params
|
135
|
+
it "should not build a single resource when on custom collection action even with id" do
|
136
|
+
@params.merge!(:action => "sort", :id => 123)
|
137
137
|
resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
|
138
138
|
resource.load_resource
|
139
139
|
@controller.instance_variable_get(:@project).should be_nil
|
140
140
|
end
|
141
141
|
|
142
|
+
it "should load a collection resource when on custom action with no id param" do
|
143
|
+
stub(Project).accessible_by(@ability, :sort) { :found_projects }
|
144
|
+
@params[:action] = "sort"
|
145
|
+
resource = CanCan::ControllerResource.new(@controller)
|
146
|
+
resource.load_resource
|
147
|
+
@controller.instance_variable_get(:@project).should be_nil
|
148
|
+
@controller.instance_variable_get(:@projects).should == :found_projects
|
149
|
+
end
|
150
|
+
|
142
151
|
it "should build a resource when on custom new action even when params[:id] exists" do
|
143
152
|
@params.merge!(:action => "build", :id => 123)
|
144
153
|
stub(Project).new { :some_project }
|
@@ -250,7 +259,7 @@ describe CanCan::ControllerResource do
|
|
250
259
|
end
|
251
260
|
|
252
261
|
it "should find record through has_one association with :singleton option" do
|
253
|
-
@params.merge!(:action => "show")
|
262
|
+
@params.merge!(:action => "show", :id => 123)
|
254
263
|
category = Object.new
|
255
264
|
@controller.instance_variable_set(:@category, category)
|
256
265
|
stub(category).project { :some_project }
|
@@ -12,7 +12,7 @@ describe CanCan::InheritedResource do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "show should load resource through @controller.resource" do
|
15
|
-
@params
|
15
|
+
@params.merge!(:action => "show", :id => 123)
|
16
16
|
stub(@controller).resource { :project_resource }
|
17
17
|
CanCan::InheritedResource.new(@controller).load_resource
|
18
18
|
@controller.instance_variable_get(:@project).should == :project_resource
|
@@ -25,17 +25,17 @@ describe CanCan::InheritedResource do
|
|
25
25
|
@controller.instance_variable_get(:@project).should == :project_resource
|
26
26
|
end
|
27
27
|
|
28
|
-
it "index should load through @controller.
|
28
|
+
it "index should load through @controller.association_chain when parent" do
|
29
29
|
@params[:action] = "index"
|
30
|
-
stub(@controller).
|
30
|
+
stub(@controller).association_chain { @controller.instance_variable_set(:@project, :project_resource) }
|
31
31
|
CanCan::InheritedResource.new(@controller, :parent => true).load_resource
|
32
32
|
@controller.instance_variable_get(:@project).should == :project_resource
|
33
33
|
end
|
34
34
|
|
35
|
-
it "index should load through @controller.
|
35
|
+
it "index should load through @controller.collection" do
|
36
36
|
@params[:action] = "index"
|
37
|
-
stub(Project).accessible_by(@ability) { :projects }
|
38
|
-
stub(@controller).
|
37
|
+
stub(Project).accessible_by(@ability, :index) { :projects }
|
38
|
+
stub(@controller).collection { Project }
|
39
39
|
CanCan::InheritedResource.new(@controller).load_resource
|
40
40
|
@controller.instance_variable_get(:@projects).should == :projects
|
41
41
|
end
|
@@ -19,8 +19,10 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|
19
19
|
|
20
20
|
with_model :article do
|
21
21
|
table do |t|
|
22
|
+
t.string "name"
|
22
23
|
t.boolean "published"
|
23
24
|
t.boolean "secret"
|
25
|
+
t.integer "priority"
|
24
26
|
t.integer "category_id"
|
25
27
|
end
|
26
28
|
model do
|
@@ -110,10 +112,25 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|
110
112
|
@ability.can :read, Article, :published => true
|
111
113
|
@ability.can :read, Article, ["secret=?", true]
|
112
114
|
article1 = Article.create!(:published => true, :secret => false)
|
115
|
+
article2 = Article.create!(:published => true, :secret => true)
|
116
|
+
article3 = Article.create!(:published => false, :secret => true)
|
113
117
|
article4 = Article.create!(:published => false, :secret => false)
|
118
|
+
Article.accessible_by(@ability).should == [article1, article2, article3]
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should allow a scope for conditions" do
|
122
|
+
@ability.can :read, Article, Article.where(:secret => true)
|
123
|
+
article1 = Article.create!(:secret => true)
|
124
|
+
article2 = Article.create!(:secret => false)
|
114
125
|
Article.accessible_by(@ability).should == [article1]
|
115
126
|
end
|
116
127
|
|
128
|
+
it "should raise an exception when trying to merge scope with other conditions" do
|
129
|
+
@ability.can :read, Article, :published => true
|
130
|
+
@ability.can :read, Article, Article.where(:secret => true)
|
131
|
+
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read Article ability.")
|
132
|
+
end
|
133
|
+
|
117
134
|
it "should not allow to fetch records when ability with just block present" do
|
118
135
|
@ability.can :read, Article do
|
119
136
|
false
|
@@ -199,5 +216,48 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|
199
216
|
@ability.can :read, Article, :project => { :admin => true }
|
200
217
|
@ability.model_adapter(Article, :read).joins.should == [:project]
|
201
218
|
end
|
219
|
+
|
220
|
+
it "should restrict articles given a MetaWhere condition" do
|
221
|
+
@ability.can :read, Article, :priority.lt => 2
|
222
|
+
article1 = Article.create!(:priority => 1)
|
223
|
+
article2 = Article.create!(:priority => 3)
|
224
|
+
Article.accessible_by(@ability).should == [article1]
|
225
|
+
@ability.should be_able_to(:read, article1)
|
226
|
+
@ability.should_not be_able_to(:read, article2)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should match any MetaWhere condition" do
|
230
|
+
adapter = CanCan::ModelAdapters::ActiveRecordAdapter
|
231
|
+
article1 = Article.new(:priority => 1, :name => "Hello World")
|
232
|
+
adapter.matches_condition?(article1, :priority.eq, 1).should be_true
|
233
|
+
adapter.matches_condition?(article1, :priority.eq, 2).should be_false
|
234
|
+
adapter.matches_condition?(article1, :priority.eq_any, [1, 2]).should be_true
|
235
|
+
adapter.matches_condition?(article1, :priority.eq_any, [2, 3]).should be_false
|
236
|
+
adapter.matches_condition?(article1, :priority.eq_all, [1, 1]).should be_true
|
237
|
+
adapter.matches_condition?(article1, :priority.eq_all, [1, 2]).should be_false
|
238
|
+
adapter.matches_condition?(article1, :priority.ne, 2).should be_true
|
239
|
+
adapter.matches_condition?(article1, :priority.ne, 1).should be_false
|
240
|
+
adapter.matches_condition?(article1, :priority.in, [1, 2]).should be_true
|
241
|
+
adapter.matches_condition?(article1, :priority.in, [2, 3]).should be_false
|
242
|
+
adapter.matches_condition?(article1, :priority.nin, [2, 3]).should be_true
|
243
|
+
adapter.matches_condition?(article1, :priority.nin, [1, 2]).should be_false
|
244
|
+
adapter.matches_condition?(article1, :priority.lt, 2).should be_true
|
245
|
+
adapter.matches_condition?(article1, :priority.lt, 1).should be_false
|
246
|
+
adapter.matches_condition?(article1, :priority.lteq, 1).should be_true
|
247
|
+
adapter.matches_condition?(article1, :priority.lteq, 0).should be_false
|
248
|
+
adapter.matches_condition?(article1, :priority.gt, 0).should be_true
|
249
|
+
adapter.matches_condition?(article1, :priority.gt, 1).should be_false
|
250
|
+
adapter.matches_condition?(article1, :priority.gteq, 1).should be_true
|
251
|
+
adapter.matches_condition?(article1, :priority.gteq, 2).should be_false
|
252
|
+
adapter.matches_condition?(article1, :name.like, "%ello worl%").should be_true
|
253
|
+
adapter.matches_condition?(article1, :name.like, "hello world").should be_true
|
254
|
+
adapter.matches_condition?(article1, :name.like, "hello%").should be_true
|
255
|
+
adapter.matches_condition?(article1, :name.like, "h%d").should be_true
|
256
|
+
adapter.matches_condition?(article1, :name.like, "%helo%").should be_false
|
257
|
+
adapter.matches_condition?(article1, :name.like, "hello").should be_false
|
258
|
+
adapter.matches_condition?(article1, :name.like, "hello.world").should be_false
|
259
|
+
adapter.matches_condition?(article1, :name.nlike, "%helo%").should be_true
|
260
|
+
adapter.matches_condition?(article1, :name.nlike, "%ello worl%").should be_false
|
261
|
+
end
|
202
262
|
end
|
203
263
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cancan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 1.6.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ryan Bates
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-03-10 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
161
|
requirements: []
|
162
162
|
|
163
163
|
rubyforge_project: cancan
|
164
|
-
rubygems_version: 1.
|
164
|
+
rubygems_version: 1.4.2
|
165
165
|
signing_key:
|
166
166
|
specification_version: 3
|
167
167
|
summary: Simple authorization solution for Rails.
|