cancan 1.5.0 → 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 CHANGED
@@ -1,3 +1,31 @@
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
+
22
+ 1.5.1 (January 20, 2011)
23
+
24
+ * Fixing deeply nested conditions in Active Record adapter - see issue #246
25
+
26
+ * Improving Mongoid support for multiple can and cannot definitions (thanks stellard) - see issue #239
27
+
28
+
1
29
  1.5.0 (January 11, 2011)
2
30
 
3
31
  * Added an Ability generator - see issue #170
data/Gemfile CHANGED
@@ -2,16 +2,17 @@ source "http://rubygems.org"
2
2
 
3
3
  case ENV["MODEL_ADAPTER"]
4
4
  when nil, "active_record"
5
- gem "sqlite3-ruby", :require => "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"
11
12
  gem "dm-migrations", "~> 1.0.2"
12
13
  when "mongoid"
13
14
  gem "bson_ext", "~> 1.1"
14
- gem "mongoid", "~> 2.0.0.beta.19"
15
+ gem "mongoid", "~> 2.0.0.beta.20"
15
16
  else
16
17
  raise "Unknown model adapter: #{ENV["MODEL_ADAPTER"]}"
17
18
  end
data/README.rdoc CHANGED
@@ -7,7 +7,7 @@ CanCan is an authorization library for Ruby on Rails which restricts what resour
7
7
 
8
8
  == Installation
9
9
 
10
- In <b>Rails 3</b>, add this to your Gemfile.
10
+ In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
11
11
 
12
12
  gem "cancan"
13
13
 
@@ -22,13 +22,19 @@ Alternatively, you can install it as a plugin.
22
22
 
23
23
  == Getting Started
24
24
 
25
- CanCan expects a +current_user+ method to exist in controllers. First, set up some authentication (such as Authlogic[https://github.com/binarylogic/authlogic] or Devise[https://github.com/plataformatec/devise]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/changing-defaults] if you need to customize this behavior.
25
+ CanCan expects a +current_user+ method to exist in the controller. First, set up some authentication (such as Authlogic[https://github.com/binarylogic/authlogic] or Devise[https://github.com/plataformatec/devise]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/changing-defaults] if you need different behavior.
26
26
 
27
- Next, make an +Ability+ class. CanCan 1.5 includes a generator for this.
27
+
28
+ === 1. Define Abilities
29
+
30
+ User permissions are defined in an +Ability+ class. CanCan 1.5 includes a Rails 3 generator for creating this class.
28
31
 
29
32
  rails g cancan:ability
30
33
 
31
- This is where the user permission will be defined. See the comments in models/ability.rb and {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details.
34
+ See {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details.
35
+
36
+
37
+ === 2. Check Abilities & Authorization
32
38
 
33
39
  The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
34
40
 
@@ -38,14 +44,14 @@ The current user's permissions can then be checked using the <tt>can?</tt> and <
38
44
 
39
45
  See {Checking Abilities}[https://github.com/ryanb/cancan/wiki/checking-abilities] for more information
40
46
 
41
- The "authorize!" method in the controller will raise an exception if the user is not able to perform the given action.
47
+ The <tt>authorize!</tt> method in the controller will raise an exception if the user is not able to perform the given action.
42
48
 
43
49
  def show
44
50
  @article = Article.find(params[:id])
45
51
  authorize! :read, @article
46
52
  end
47
53
 
48
- Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for each action.
54
+ Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.
49
55
 
50
56
  class ArticlesController < ApplicationController
51
57
  load_and_authorize_resource
@@ -57,21 +63,34 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_
57
63
 
58
64
  See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information.
59
65
 
66
+
67
+ === 3. Handle Unauthorized Access
68
+
60
69
  If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
61
70
 
62
71
  class ApplicationController < ActionController::Base
63
72
  rescue_from CanCan::AccessDenied do |exception|
64
- flash[:alert] = exception.message
65
- redirect_to root_url
73
+ redirect_to root_url, :alert => exception.message
66
74
  end
67
75
  end
68
76
 
69
77
  See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
70
78
 
71
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
+
72
91
  == Wiki Docs
73
92
 
74
- * {Upgrading to 1.5}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.5]
93
+ * {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
75
94
  * {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
76
95
  * {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
77
96
  * {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
@@ -82,9 +101,9 @@ See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling
82
101
 
83
102
  == Questions or Problems?
84
103
 
85
- If you have any issues with CanCan which you cannot find the solution to in the documentation, please add an {issue on GitHub}[https://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
104
+ If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/ryanb/cancan/wiki], please add an {issue on GitHub}[https://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
86
105
 
87
- To get the specs running you should call +bundle+ and then +rake+. Specs currently do not work in Ruby 1.9 due to the RR mocking framework. See the {spec/README}[https://github.com/ryanb/cancan/blob/master/spec/README.rdoc] for more information.
106
+ To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/ryanb/cancan/blob/master/spec/README.rdoc] for more information.
88
107
 
89
108
 
90
109
  == Special Thanks
@@ -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
- def check_authorization(*args)
227
- self.after_filter(*args) do |controller|
228
- unless controller.instance_variable_defined?(:@_authorized)
229
- raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
230
- end
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
- # flash[:error] = exception.message
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
- controller_class.before_filter(options.slice(:only, :except)) do |controller|
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? @params[:action].to_sym
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 :parent
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 :end_of_association_chain
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
  #
@@ -31,12 +62,13 @@ module CanCan
31
62
  end
32
63
  end
33
64
 
34
- def tableized_conditions(conditions)
65
+ def tableized_conditions(conditions, model_class = @model_class)
35
66
  return conditions unless conditions.kind_of? Hash
36
67
  conditions.inject({}) do |result_hash, (name, value)|
37
68
  if value.kind_of? Hash
38
- name = @model_class.reflect_on_association(name).table_name
39
- value = tableized_conditions(value)
69
+ association_class = model_class.reflect_on_association(name).class_name.constantize
70
+ name = model_class.reflect_on_association(name).table_name.to_sym
71
+ value = tableized_conditions(value, association_class)
40
72
  end
41
73
  result_hash[name] = value
42
74
  result_hash
@@ -54,7 +86,9 @@ module CanCan
54
86
  end
55
87
 
56
88
  def database_records
57
- if @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
89
+ if override_scope
90
+ override_scope
91
+ elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
58
92
  @model_class.where(conditions).joins(joins)
59
93
  else
60
94
  @model_class.scoped(:conditions => conditions, :joins => joins)
@@ -63,6 +97,18 @@ module CanCan
63
97
 
64
98
  private
65
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
+
66
112
  def merge_conditions(sql, conditions_hash, behavior)
67
113
  if conditions_hash.blank?
68
114
  behavior ? true_sql : false_sql
@@ -16,21 +16,18 @@ module CanCan
16
16
  end
17
17
 
18
18
  def database_records
19
- @model_class.where(conditions)
20
- end
21
-
22
- def conditions
23
- if @rules.size == 0
24
- false_query
19
+ if @rules.size == 0
20
+ @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
25
21
  else
26
- @rules.first.conditions
22
+ @rules.inject(@model_class.all) do |records, rule|
23
+ if rule.base_behavior
24
+ records.or(rule.conditions)
25
+ else
26
+ records.excludes(rule.conditions)
27
+ end
28
+ end
27
29
  end
28
30
  end
29
-
30
- def false_query
31
- # this query is sure to return no results
32
- {:_id => {'$exists' => false, '$type' => 7}} # type 7 is an ObjectID (default for _id)
33
- end
34
31
  end
35
32
  end
36
33
  end
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.kind_of? Hash
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.kind_of?(Hash)
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
- attribute = subject.send(name)
104
- if value.kind_of?(Hash)
105
- if attribute.kind_of? Array
106
- attribute.any? { |element| matches_conditions_hash? element, value }
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
- matches_conditions_hash? attribute, value
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
@@ -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(:some_options) { |options, block| block.call(@controller) }
69
+ mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
65
70
  lambda {
66
- @controller_class.check_authorization(:some_options)
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(:some_options) { |options, block| block.call(@controller) }
93
+ mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
73
94
  lambda {
74
- @controller_class.check_authorization(:some_options)
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[:action] = "show"
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[:action] = "show"
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[:action] = "show"
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[:action] = "sort"
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[:action] = "show"
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.parent when parent" do
28
+ it "index should load through @controller.association_chain when parent" do
29
29
  @params[:action] = "index"
30
- stub(@controller).parent { :project_resource }
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.end_of_association_chain" do
35
+ it "index should load through @controller.collection" do
36
36
  @params[:action] = "index"
37
- stub(Project).accessible_by(@ability) { :projects }
38
- stub(@controller).end_of_association_chain { Project }
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
@@ -8,12 +8,25 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
8
8
  ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
9
9
 
10
10
  describe CanCan::ModelAdapters::ActiveRecordAdapter do
11
+ with_model :category do
12
+ table do |t|
13
+ t.boolean "visible"
14
+ end
15
+ model do
16
+ has_many :articles
17
+ end
18
+ end
19
+
11
20
  with_model :article do
12
21
  table do |t|
22
+ t.string "name"
13
23
  t.boolean "published"
14
24
  t.boolean "secret"
25
+ t.integer "priority"
26
+ t.integer "category_id"
15
27
  end
16
28
  model do
29
+ belongs_to :category
17
30
  has_many :comments
18
31
  end
19
32
  end
@@ -88,14 +101,36 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
88
101
  Comment.accessible_by(@ability).should == [comment1]
89
102
  end
90
103
 
104
+ it "should only read comments for visible categories through articles" do
105
+ @ability.can :read, Comment, :article => { :category => { :visible => true } }
106
+ comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true)))
107
+ comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false)))
108
+ Comment.accessible_by(@ability).should == [comment1]
109
+ end
110
+
91
111
  it "should allow conditions in SQL and merge with hash conditions" do
92
112
  @ability.can :read, Article, :published => true
93
113
  @ability.can :read, Article, ["secret=?", true]
94
114
  article1 = Article.create!(:published => true, :secret => false)
115
+ article2 = Article.create!(:published => true, :secret => true)
116
+ article3 = Article.create!(:published => false, :secret => true)
95
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)
96
125
  Article.accessible_by(@ability).should == [article1]
97
126
  end
98
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
+
99
134
  it "should not allow to fetch records when ability with just block present" do
100
135
  @ability.can :read, Article do
101
136
  false
@@ -181,5 +216,48 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
181
216
  @ability.can :read, Article, :project => { :admin => true }
182
217
  @ability.model_adapter(Article, :read).joins.should == [:project]
183
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
184
262
  end
185
263
  end
@@ -56,7 +56,7 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
56
56
  lord = MongoidProject.create(:title => 'Lord')
57
57
  dude = MongoidProject.create(:title => 'Dude')
58
58
 
59
- MongoidProject.accessible_by(@ability, :read).should == [sir]
59
+ MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
60
60
  end
61
61
 
62
62
  it "should return everything when the defined ability is manage all" do
@@ -154,7 +154,24 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
154
154
  @ability.can :read, MongoidProject, :foo => {:bar => 1}
155
155
  MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
156
156
  end
157
+
158
+ it "should exclude from the result if set to cannot" do
159
+ obj = MongoidProject.create(:bar => 1)
160
+ obj2 = MongoidProject.create(:bar => 2)
161
+ @ability.can :read, MongoidProject
162
+ @ability.cannot :read, MongoidProject, :bar => 2
163
+ MongoidProject.accessible_by(@ability, :read).entries.should == [obj]
164
+ end
157
165
 
166
+ it "should combine the rules" do
167
+ obj = MongoidProject.create(:bar => 1)
168
+ obj2 = MongoidProject.create(:bar => 2)
169
+ obj3 = MongoidProject.create(:bar => 3)
170
+ @ability.can :read, MongoidProject, :bar => 1
171
+ @ability.can :read, MongoidProject, :bar => 2
172
+ MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2]
173
+ end
174
+
158
175
  it "should not allow to fetch records when ability with just block present" do
159
176
  @ability.can :read, MongoidProject do
160
177
  false
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: 3
5
- prerelease: false
4
+ hash: 15
5
+ prerelease:
6
6
  segments:
7
7
  - 1
8
- - 5
8
+ - 6
9
9
  - 0
10
- version: 1.5.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-01-11 00:00:00 -08:00
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.3.7
164
+ rubygems_version: 1.4.2
165
165
  signing_key:
166
166
  specification_version: 3
167
167
  summary: Simple authorization solution for Rails.