cancan 1.5.1 → 1.6.0
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 +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.
|