cancan 1.4.0.beta1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,24 @@
1
- 1.4.0 (not yet released)
1
+ 1.4.0 (October 5, 2010)
2
+
3
+ * Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163
4
+
5
+ * Stop at 'cannot' definition when there are no conditions - see issue #161
6
+
7
+ * The :through option will now call a method with that name if instance variable doesn't exist - see issue #146
8
+
9
+ * Adding :shallow option to load_resource to bring back old behavior of fetching a child without a parent
10
+
11
+ * Raise AccessDenied error when loading a child and parent resource isn't found
12
+
13
+ * Abilities defined on a module will apply to anything that includes that module - see issue #150 and #152
14
+
15
+ * Abilities can be defined with a string of SQL in addition to a block so accessible_by works with a block - see issue #150
16
+
17
+ * Adding better support for InheritedResource - see issue #23
18
+
19
+ * Loading the collection instance variable (for index action) using accessible_by - see issue #137
20
+
21
+ * Adding action and subject variables to I18n unauthorized message - closes #142
2
22
 
3
23
  * Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed (thanks justinko) - see issue #135
4
24
 
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Ryan Bates
1
+ Copyright (c) 2010 Ryan Bates
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -2,29 +2,29 @@
2
2
 
3
3
  Wiki[http://wiki.github.com/ryanb/cancan] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
4
4
 
5
- CanCan is an authorization solution for Ruby on Rails for restricting what a given user is allowed to access throughout the application. It does not care how your user roles are defined, it simply focusses on keeping permission logic in a single location (the +Ability+ class) so it is not duplicated across controllers, views, and database queries.
6
-
7
- By default, the +current_user+ method is required, so if you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
5
+ CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the +Ability+ class) and not duplicated across controllers, views, and database queries.
8
6
 
9
7
 
10
8
  == Installation
11
9
 
12
- To install CanCan, include the gem in the environment.rb in Rails 2.3.
10
+ In <b>Rails 3</b>, add this to your Gemfile.
13
11
 
14
- config.gem "cancan"
12
+ gem "cancan"
15
13
 
16
- Or the Gemfile in Rails 3.
14
+ In <b>Rails 2</b>, add this to your environment.rb file.
17
15
 
18
- gem "cancan"
16
+ config.gem "cancan"
19
17
 
20
- Alternatively it can be installed as a plugin.
18
+ Alternatively, you can install it as a plugin.
21
19
 
22
- script/plugin install git://github.com/ryanb/cancan.git
20
+ rails plugin install git://github.com/ryanb/cancan.git
23
21
 
24
22
 
25
23
  == Getting Started
26
24
 
27
- First, define a class called +Ability+ in "models/ability.rb" or anywhere else in the load path. It should look something like this.
25
+ CanCan expects a +current_user+ method to exist. If you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
26
+
27
+ Next create a class called +Ability+ in "models/ability.rb" or anywhere else in the load path. It should look similar to this.
28
28
 
29
29
  class Ability
30
30
  include CanCan::Ability
@@ -38,7 +38,7 @@ First, define a class called +Ability+ in "models/ability.rb" or anywhere else i
38
38
  end
39
39
  end
40
40
 
41
- This is where all permissions will go. See the "Defining Abilities" section below for more information.
41
+ The +current_user+ is passed in to this method which is where the abilities are defined. See the "Defining Abilities" section below for more information.
42
42
 
43
43
  The current user's permissions can be accessed using the "can?" and "cannot?" methods in the view and controller.
44
44
 
@@ -67,11 +67,11 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_
67
67
 
68
68
  See {Authorizing Controller Actions}[http://wiki.github.com/ryanb/cancan/authorizing-controller-actions] for more information
69
69
 
70
- If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
70
+ 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+.
71
71
 
72
72
  class ApplicationController < ActionController::Base
73
73
  rescue_from CanCan::AccessDenied do |exception|
74
- flash[:error] = exception.message
74
+ flash[:alert] = exception.message
75
75
  redirect_to root_url
76
76
  end
77
77
  end
@@ -81,7 +81,7 @@ See {Exception Handling}[http://wiki.github.com/ryanb/cancan/exception-handling]
81
81
 
82
82
  == Defining Abilities
83
83
 
84
- As shown above, the +Ability+ class is where all user permissions are defined. The user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no assumptions about how roles are handled in your application. See {Role Based Authorization}[http://wiki.github.com/ryanb/cancan/role-based-authorization] for an example.
84
+ As shown above, the +Ability+ class is where all user permissions are defined. The current user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no assumption about how roles are handled in your application. See {Role Based Authorization}[http://wiki.github.com/ryanb/cancan/role-based-authorization] for an example.
85
85
 
86
86
  The +can+ method is used to define permissions and requires two arguments. The first one is the action you're setting the permission for, the second one is the class of object you're setting it on.
87
87
 
@@ -97,7 +97,7 @@ Use :+manage+ to represent any action and :+all+ to represent any class. Here ar
97
97
  can :read, :all # has permission to read any model
98
98
  can :manage, :all # has permission to do anything to any model
99
99
 
100
- You can pass a hash of conditions as the third argument to further restrict what the user is able to access. Here the user will only have permission to read active projects which he owns.
100
+ You can pass a hash of conditions as the third argument to further define what the user is able to access. Here the user will only have permission to read active projects which he owns.
101
101
 
102
102
  can :read, Project, :active => true, :user_id => user.id
103
103
 
@@ -106,10 +106,10 @@ See {Defining Abilities with Hashes}[http://wiki.github.com/ryanb/cancan/definin
106
106
  Blocks can also be used if you need more control.
107
107
 
108
108
  can :update, Project do |project|
109
- project && project.groups.include?(user.group)
109
+ project.groups.include?(user.group)
110
110
  end
111
111
 
112
- If the block returns true then the user has that :+update+ ability for that project, otherwise he will be denied access. See {Defining Abilities with Blocks}[http://wiki.github.com/ryanb/cancan/defining-abilities-with-blocks] for more information.
112
+ If the block returns true then the user has that ability for that project, otherwise he will be denied access. See {Defining Abilities with Blocks}[http://wiki.github.com/ryanb/cancan/defining-abilities-with-blocks] for more information.
113
113
 
114
114
 
115
115
  == Aliasing Actions
@@ -120,7 +120,7 @@ You will usually be working with four actions when defining and checking permiss
120
120
  alias_action :new, :to => :create
121
121
  alias_action :edit, :to => :update
122
122
 
123
- Notice the +edit+ action is aliased to +update+. If the user is able to update a record he also has permission to edit it. You can define your own aliases in the +Ability+ class
123
+ Notice the +edit+ action is aliased to +update+. This means if the user is able to update a record he also has permission to edit it. You can define your own aliases in the +Ability+ class.
124
124
 
125
125
  alias_action :update, :destroy, :to => :modify
126
126
  can :modify, Comment
@@ -131,22 +131,32 @@ The +alias_action+ method is an instance method and usually called in +initializ
131
131
 
132
132
  == Fetching Records
133
133
 
134
- In the controller +index+ action you may want to fetch only the records which the user has permission to read. You can do this with the +accessible_by+ scope.
134
+ It is possible to fetch records which the user has permission to read using the +accessible_by+ scope in Active Record.
135
135
 
136
136
  @articles = Article.accessible_by(current_ability)
137
137
 
138
+ Since version 1.4 this is done automatically when loading resources in the index action, so one rarely needs to do it manually.
139
+
138
140
  This will only work when abilities are defined using hash conditions, not blocks. See {Fetching Records}[http://wiki.github.com/ryanb/cancan/fetching-records] for more information.
139
141
 
140
142
 
141
143
  == Additional Docs
142
144
 
143
- * {Upgrading to 1.3}[http://wiki.github.com/ryanb/cancan/upgrading-to-13]
145
+ * {Upgrading to 1.4}[http://github.com/ryanb/cancan/wiki/Upgrading-to-1.4]
144
146
  * {Nested Resources}[http://wiki.github.com/ryanb/cancan/nested-resources]
145
147
  * {Testing Abilities}[http://wiki.github.com/ryanb/cancan/testing-abilities]
146
148
  * {Accessing Request Data}[http://wiki.github.com/ryanb/cancan/accessing-request-data]
147
149
  * {Admin Namespace}[http://wiki.github.com/ryanb/cancan/admin-namespace]
148
150
  * {See more}[http://wiki.github.com/ryanb/cancan/]
149
151
 
152
+
153
+ == Questions or Problems?
154
+
155
+ If you have any issues with CanCan which you cannot find the solution to in the documentation, please add an {issue on GitHub}[http://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
156
+
157
+ 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.
158
+
159
+
150
160
  == Special Thanks
151
161
 
152
162
  CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[http://github.com/ryanb/cancan/contributors]. See the CHANGELOG[http://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.
data/Rakefile CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'spec/rake/spectask'
3
+ require 'rspec/core/rake_task'
4
4
 
5
- spec_files = Rake::FileList["spec/**/*_spec.rb"]
6
-
7
- desc "Run specs"
8
- Spec::Rake::SpecTask.new do |t|
9
- t.spec_files = spec_files
10
- t.spec_opts = ["-c"]
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
11
8
  end
12
9
 
13
10
  task :default => :spec
@@ -5,3 +5,4 @@ require 'cancan/controller_additions'
5
5
  require 'cancan/active_record_additions'
6
6
  require 'cancan/exceptions'
7
7
  require 'cancan/query'
8
+ require 'cancan/inherited_resource'
@@ -54,7 +54,7 @@ module CanCan
54
54
  #
55
55
  # Also see the RSpec Matchers to aid in testing.
56
56
  def can?(action, subject, *extra_args)
57
- match = relevant_can_definitions(action, subject).detect do |can_definition|
57
+ match = relevant_can_definitions_for_match(action, subject).detect do |can_definition|
58
58
  can_definition.matches_conditions?(action, subject, extra_args)
59
59
  end
60
60
  match ? match.base_behavior : false
@@ -207,7 +207,9 @@ module CanCan
207
207
 
208
208
  def unauthorized_message(action, subject)
209
209
  keys = unauthorized_message_keys(action, subject)
210
- message = I18n.translate(nil, :scope => :unauthorized, :default => keys + [""])
210
+ variables = {:action => action.to_s}
211
+ variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
212
+ message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
211
213
  message.blank? ? nil : message
212
214
  end
213
215
 
@@ -219,6 +221,14 @@ module CanCan
219
221
  attributes
220
222
  end
221
223
 
224
+ def has_block?(action, subject)
225
+ relevant_can_definitions(action, subject).any?(&:only_block?)
226
+ end
227
+
228
+ def has_raw_sql?(action, subject)
229
+ relevant_can_definitions(action, subject).any?(&:only_raw_sql?)
230
+ end
231
+
222
232
  private
223
233
 
224
234
  def unauthorized_message_keys(action, subject)
@@ -262,6 +272,14 @@ module CanCan
262
272
  end
263
273
  end
264
274
 
275
+ def relevant_can_definitions_for_match(action, subject)
276
+ relevant_can_definitions(action, subject).each do |can_definition|
277
+ if can_definition.only_raw_sql?
278
+ raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
279
+ end
280
+ end
281
+ end
282
+
265
283
  def relevant_can_definitions_for_query(action, subject)
266
284
  relevant_can_definitions(action, subject).each do |can_definition|
267
285
  if can_definition.only_block?
@@ -36,11 +36,13 @@ module CanCan
36
36
  elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
37
37
  matches_conditions_hash?(subject)
38
38
  else
39
- @base_behavior
39
+ # Don't stop at "cannot" definitions when there are conditions.
40
+ @conditions.empty? ? true : @base_behavior
40
41
  end
41
42
  end
42
43
 
43
44
  def tableized_conditions(conditions = @conditions)
45
+ return conditions unless conditions.kind_of? Hash
44
46
  conditions.inject({}) do |result_hash, (name, value)|
45
47
  if value.kind_of? Hash
46
48
  name = name.to_s.tableize.to_sym
@@ -55,6 +57,10 @@ module CanCan
55
57
  conditions_empty? && !@block.nil?
56
58
  end
57
59
 
60
+ def only_raw_sql?
61
+ @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
62
+ end
63
+
58
64
  def conditions_empty?
59
65
  @conditions == {} || @conditions.nil?
60
66
  end
@@ -63,7 +69,7 @@ module CanCan
63
69
  hash = {}
64
70
  conditions.map do |name, value|
65
71
  hash[name] = associations_hash(value) if value.kind_of? Hash
66
- end
72
+ end if conditions.kind_of? Hash
67
73
  hash
68
74
  end
69
75
 
@@ -71,14 +77,15 @@ module CanCan
71
77
  attributes = {}
72
78
  @conditions.each do |key, value|
73
79
  attributes[key] = value unless [Array, Range, Hash].include? value.class
74
- end
80
+ end if @conditions.kind_of? Hash
75
81
  attributes
76
82
  end
77
83
 
78
84
  private
79
85
 
80
86
  def subject_class?(subject)
81
- (subject.kind_of?(Hash) ? subject.values.first : subject).class == Class
87
+ klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
88
+ klass == Class || klass == Module
82
89
  end
83
90
 
84
91
  def matches_action?(action)
@@ -90,7 +97,7 @@ module CanCan
90
97
  end
91
98
 
92
99
  def matches_subject_class?(subject)
93
- @subjects.any? { |sub| sub.kind_of?(Class) && (subject.kind_of?(sub) || subject.kind_of?(Class) && subject.ancestors.include?(sub)) }
100
+ @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
94
101
  end
95
102
 
96
103
  def matches_conditions_hash?(subject, conditions = @conditions)
@@ -12,14 +12,14 @@ module CanCan
12
12
  # end
13
13
  #
14
14
  def load_and_authorize_resource(*args)
15
- ControllerResource.add_before_filter(self, :load_and_authorize_resource, *args)
15
+ cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
16
16
  end
17
17
 
18
18
  # Sets up a before filter which loads the model resource into an instance variable.
19
19
  # For example, given an ArticlesController it will load the current article into the @article
20
20
  # instance variable. It does this by either calling Article.find(params[:id]) or
21
- # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
22
- # action.
21
+ # Article.new(params[:article]) depending upon the action. The index action will
22
+ # automatically set @articles to Article.accessible_by(current_ability).
23
23
  #
24
24
  # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
25
25
  # the initial attributes based on these conditions. This way these actions will satisfy
@@ -69,7 +69,10 @@ module CanCan
69
69
  # Does not apply before filter to given actions.
70
70
  #
71
71
  # [:+through+]
72
- # Load this resource through another one. This should match the name of the parent instance variable.
72
+ # Load this resource through another one. This should match the name of the parent instance variable or method.
73
+ #
74
+ # [:+shallow+]
75
+ # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
73
76
  #
74
77
  # [:+singleton+]
75
78
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
@@ -103,7 +106,7 @@ module CanCan
103
106
  # load_resource :new => :build
104
107
  #
105
108
  def load_resource(*args)
106
- ControllerResource.add_before_filter(self, :load_resource, *args)
109
+ cancan_resource_class.add_before_filter(self, :load_resource, *args)
107
110
  end
108
111
 
109
112
  # Sets up a before filter which authorizes the resource using the instance variable.
@@ -156,7 +159,7 @@ module CanCan
156
159
  # Authorize conditions on this parent resource when instance isn't available.
157
160
  #
158
161
  def authorize_resource(*args)
159
- ControllerResource.add_before_filter(self, :authorize_resource, *args)
162
+ cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
160
163
  end
161
164
 
162
165
  # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
@@ -190,6 +193,14 @@ module CanCan
190
193
  controller.instance_variable_set(:@_authorized, true)
191
194
  end
192
195
  end
196
+
197
+ def cancan_resource_class
198
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
199
+ InheritedResource
200
+ else
201
+ ControllerResource
202
+ end
203
+ end
193
204
  end
194
205
 
195
206
  def self.included(base)
@@ -215,7 +226,7 @@ module CanCan
215
226
  # en:
216
227
  # unauthorized:
217
228
  # manage:
218
- # all: "Not authorized to perform that action."
229
+ # all: "Not authorized to %{action} %{subject}."
219
230
  # user: "Not allowed to manage other user accounts."
220
231
  # update:
221
232
  # project: "Not allowed to update this project."
@@ -6,7 +6,7 @@ module CanCan
6
6
  options = args.extract_options!
7
7
  resource_name = args.first
8
8
  controller_class.before_filter(options.slice(:only, :except)) do |controller|
9
- ControllerResource.new(controller, resource_name, options.except(:only, :except)).send(method)
9
+ controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
10
10
  end
11
11
  end
12
12
 
@@ -26,8 +26,10 @@ module CanCan
26
26
  end
27
27
 
28
28
  def load_resource
29
- if !resource_instance && (parent? || member_action?)
30
- @controller.instance_variable_set("@#{instance_name}", load_resource_instance)
29
+ if parent? || member_action?
30
+ self.resource_instance ||= load_resource_instance
31
+ elsif load_collection?
32
+ self.collection_instance ||= load_collection
31
33
  end
32
34
  end
33
35
 
@@ -39,7 +41,7 @@ module CanCan
39
41
  @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
40
42
  end
41
43
 
42
- private
44
+ protected
43
45
 
44
46
  def load_resource_instance
45
47
  if !parent? && new_actions.include?(@params[:action].to_sym)
@@ -49,6 +51,15 @@ module CanCan
49
51
  end
50
52
  end
51
53
 
54
+ def load_collection?
55
+ resource_base.respond_to?(:accessible_by) &&
56
+ !current_ability.has_block?(authorization_action, resource_class)
57
+ end
58
+
59
+ def load_collection
60
+ resource_base.accessible_by(current_ability)
61
+ end
62
+
52
63
  def build_resource
53
64
  resource = resource_base.send(@options[:singleton] ? "build_#{name}" : "new")
54
65
  initial_attributes.each do |name, value|
@@ -59,7 +70,7 @@ module CanCan
59
70
  end
60
71
 
61
72
  def initial_attributes
62
- @controller.current_ability.attributes_for(@params[:action].to_sym, resource_class)
73
+ current_ability.attributes_for(@params[:action].to_sym, resource_class)
63
74
  end
64
75
 
65
76
  def find_resource
@@ -98,16 +109,35 @@ module CanCan
98
109
  parent_resource ? {parent_resource => resource_class} : resource_class
99
110
  end
100
111
 
112
+ def resource_instance=(instance)
113
+ @controller.instance_variable_set("@#{instance_name}", instance)
114
+ end
115
+
101
116
  def resource_instance
102
117
  @controller.instance_variable_get("@#{instance_name}")
103
118
  end
104
119
 
120
+ def collection_instance=(instance)
121
+ @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
122
+ end
123
+
124
+ def collection_instance
125
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
126
+ end
127
+
105
128
  # The object that methods (such as "find", "new" or "build") are called on.
106
129
  # If the :through option is passed it will go through an association on that instance.
130
+ # If the :shallow option is passed it will use the resource_class if there's no parent
107
131
  # If the :singleton option is passed it won't use the association because it needs to be handled later.
108
132
  def resource_base
109
- if parent_resource
110
- @options[:singleton] ? parent_resource : parent_resource.send(name.to_s.pluralize)
133
+ if @options[:through]
134
+ if parent_resource
135
+ @options[:singleton] ? parent_resource : parent_resource.send(name.to_s.pluralize)
136
+ elsif @options[:shallow]
137
+ resource_class
138
+ else
139
+ raise AccessDenied # maybe this should be a record not found error instead?
140
+ end
111
141
  else
112
142
  resource_class
113
143
  end
@@ -115,7 +145,19 @@ module CanCan
115
145
 
116
146
  # The object to load this resource through.
117
147
  def parent_resource
118
- @options[:through] && [@options[:through]].flatten.map { |i| @controller.instance_variable_get("@#{i}") }.compact.first
148
+ @options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first
149
+ end
150
+
151
+ def fetch_parent(name)
152
+ if @controller.instance_variable_defined? "@#{name}"
153
+ @controller.instance_variable_get("@#{name}")
154
+ elsif @controller.respond_to? name
155
+ @controller.send(name)
156
+ end
157
+ end
158
+
159
+ def current_ability
160
+ @controller.send(:current_ability)
119
161
  end
120
162
 
121
163
  def name
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ # For use with Inherited Resources
3
+ class InheritedResource < ControllerResource # :nodoc:
4
+ def load_resource_instance
5
+ if parent?
6
+ @controller.send :parent
7
+ elsif new_actions.include? @params[:action].to_sym
8
+ @controller.send :build_resource
9
+ else
10
+ @controller.send :resource
11
+ end
12
+ end
13
+
14
+ def resource_base
15
+ @controller.send :end_of_association_chain
16
+ end
17
+ end
18
+ end
@@ -24,6 +24,14 @@ describe CanCan::Ability do
24
24
  @ability.can?(:read, :some_symbol).should == true
25
25
  end
26
26
 
27
+ it "should pass nil to a block when no instance is passed" do
28
+ @ability.can :read, Symbol do |sym|
29
+ sym.should be_nil
30
+ true
31
+ end
32
+ @ability.can?(:read, Symbol).should be_true
33
+ end
34
+
27
35
  it "should pass to previous can definition, if block returns false or nil" do
28
36
  @ability.can :read, Symbol
29
37
  @ability.can :read, Integer do |i|
@@ -250,6 +258,32 @@ describe CanCan::Ability do
250
258
  @ability.can?(:read, Range).should be_true
251
259
  end
252
260
 
261
+ it "should stop at cannot definition when no hash is present" do
262
+ @ability.can :read, :all
263
+ @ability.cannot :read, Range
264
+ @ability.can?(:read, 1..5).should be_false
265
+ @ability.can?(:read, Range).should be_false
266
+ end
267
+
268
+ it "should allow to check ability for Module" do
269
+ module B; end
270
+ class A; include B; end
271
+ @ability.can :read, B
272
+ @ability.can?(:read, A).should be_true
273
+ @ability.can?(:read, A.new).should be_true
274
+ end
275
+
276
+ it "should pass nil to a block for ability on Module when no instance is passed" do
277
+ module B; end
278
+ class A; include B; end
279
+ @ability.can :read, B do |sym|
280
+ sym.should be_nil
281
+ true
282
+ end
283
+ @ability.can?(:read, B).should be_true
284
+ @ability.can?(:read, A).should be_true
285
+ end
286
+
253
287
  it "passing a hash of subjects should check permissions through association" do
254
288
  @ability.can :read, Range, :string => {:length => 3}
255
289
  @ability.can?(:read, "foo" => Range).should be_true
@@ -282,6 +316,22 @@ describe CanCan::Ability do
282
316
  lambda { @ability.authorize!(:read, :foo) }.should_not raise_error
283
317
  end
284
318
 
319
+ it "should know when block is used in conditions" do
320
+ @ability.can :read, :foo
321
+ @ability.should_not have_block(:read, :foo)
322
+ @ability.can :read, :foo do |foo|
323
+ false
324
+ end
325
+ @ability.should have_block(:read, :foo)
326
+ end
327
+
328
+ it "should know when raw sql is used in conditions" do
329
+ @ability.can :read, :foo
330
+ @ability.should_not have_raw_sql(:read, :foo)
331
+ @ability.can :read, :foo, 'false'
332
+ @ability.should have_raw_sql(:read, :foo)
333
+ end
334
+
285
335
  it "should raise access denied exception with default message if not specified" do
286
336
  begin
287
337
  @ability.authorize! :read, :foo
@@ -327,5 +377,11 @@ describe CanCan::Ability do
327
377
  @ability.unauthorized_message(:update, Array).should == "modify array"
328
378
  @ability.unauthorized_message(:edit, Array).should == "modify array"
329
379
  end
380
+
381
+ it "should have variables for action and subject" do
382
+ I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
383
+ @ability.unauthorized_message(:update, Array).should == "update array"
384
+ @ability.unauthorized_message(:edit, 1..3).should == "edit range"
385
+ end
330
386
  end
331
387
  end
@@ -48,4 +48,26 @@ describe CanCan::ActiveRecordAdditions do
48
48
  # @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
49
49
  @model_class.accessible_by(@ability).should == :found_records
50
50
  end
51
+
52
+ it "should allow to define sql conditions by not hash" do
53
+ @ability.can :read, @model_class, :foo => 1
54
+ @ability.can :read, @model_class, ['bar = ?', 1]
55
+ stub(@model_class).scoped( :conditions => '(bar = 1) OR (foo=1)', :joins => nil ) { :found_records }
56
+ stub(@model_class).scoped{|*args| args.inspect}
57
+ @model_class.accessible_by(@ability).should == :found_records
58
+ end
59
+
60
+ it "should not allow to fetch records when ability with just block present" do
61
+ @ability.can :read, @model_class do false end
62
+ lambda {
63
+ @model_class.accessible_by(@ability)
64
+ }.should raise_error(CanCan::Error)
65
+ end
66
+
67
+ it "should not allow to check ability on object when nonhash sql ability definition without block present" do
68
+ @ability.can :read, @model_class, ['bar = ?', 1]
69
+ lambda {
70
+ @ability.can? :read, @model_class.new
71
+ }.should raise_error(CanCan::Error)
72
+ end
51
73
  end
@@ -74,4 +74,13 @@ describe CanCan::ControllerAdditions do
74
74
  @controller_class.check_authorization(:some_options)
75
75
  }.should_not raise_error(CanCan::AuthorizationNotPerformed)
76
76
  end
77
+
78
+ it "cancan_resource_class should be ControllerResource by default" do
79
+ @controller.class.cancan_resource_class.should == CanCan::ControllerResource
80
+ end
81
+
82
+ it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do
83
+ stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
84
+ @controller.class.cancan_resource_class.should == CanCan::InheritedResource
85
+ end
77
86
  end
@@ -4,8 +4,9 @@ describe CanCan::ControllerResource do
4
4
  before(:each) do
5
5
  @params = HashWithIndifferentAccess.new(:controller => "projects")
6
6
  @controller = Object.new # simple stub for now
7
+ @ability = Ability.new(nil)
7
8
  stub(@controller).params { @params }
8
- stub(@controller).current_ability.stub!.attributes_for { {} }
9
+ stub(@controller).current_ability { @ability }
9
10
  end
10
11
 
11
12
  it "should load the resource into an instance variable if params[:id] is specified" do
@@ -49,7 +50,7 @@ describe CanCan::ControllerResource do
49
50
 
50
51
  it "should build a new resource with attributes from current ability" do
51
52
  @params.merge!(:action => "new")
52
- stub(@controller).current_ability.stub!.attributes_for(:new, Project) { {:name => "from conditions"} }
53
+ @ability.can(:create, Project, :name => "from conditions")
53
54
  resource = CanCan::ControllerResource.new(@controller)
54
55
  resource.load_resource
55
56
  @controller.instance_variable_get(:@project).name.should == "from conditions"
@@ -57,17 +58,37 @@ describe CanCan::ControllerResource do
57
58
 
58
59
  it "should override initial attributes with params" do
59
60
  @params.merge!(:action => "new", :project => {:name => "from params"})
60
- stub(@controller).current_ability.stub!.attributes_for(:new, Project) { {:name => "foobar"} }
61
+ @ability.can(:create, Project, :name => "from conditions")
61
62
  resource = CanCan::ControllerResource.new(@controller)
62
63
  resource.load_resource
63
64
  @controller.instance_variable_get(:@project).name.should == "from params"
64
65
  end
65
66
 
66
- it "should not build a resource when on index action" do
67
+ it "should build a collection when on index action when class responds to accessible_by" do
68
+ stub(Project).accessible_by(@ability) { :found_projects }
67
69
  @params[:action] = "index"
70
+ resource = CanCan::ControllerResource.new(@controller, :project)
71
+ resource.load_resource
72
+ @controller.instance_variable_get(:@project).should be_nil
73
+ @controller.instance_variable_get(:@projects).should == :found_projects
74
+ end
75
+
76
+ it "should not build a collection when on index action when class does not respond to accessible_by" do
77
+ @params[:action] = "index"
78
+ resource = CanCan::ControllerResource.new(@controller)
79
+ resource.load_resource
80
+ @controller.instance_variable_get(:@project).should be_nil
81
+ @controller.instance_variable_defined?(:@projects).should be_false
82
+ end
83
+
84
+ it "should not use accessible_by when defining abilities through a block" do
85
+ stub(Project).accessible_by(@ability) { :found_projects }
86
+ @params[:action] = "index"
87
+ @ability.can(:read, Project) { |p| false }
68
88
  resource = CanCan::ControllerResource.new(@controller)
69
89
  resource.load_resource
70
90
  @controller.instance_variable_get(:@project).should be_nil
91
+ @controller.instance_variable_defined?(:@projects).should be_false
71
92
  end
72
93
 
73
94
  it "should perform authorization using controller action and loaded model" do
@@ -143,7 +164,7 @@ describe CanCan::ControllerResource do
143
164
  @controller.instance_variable_get(:@project).should == :some_project
144
165
  end
145
166
 
146
- it "should load resource through the association of another parent resource" do
167
+ it "should load resource through the association of another parent resource using instance variable" do
147
168
  @params.merge!(:action => "show", :id => 123)
148
169
  category = Object.new
149
170
  @controller.instance_variable_set(:@category, category)
@@ -153,14 +174,34 @@ describe CanCan::ControllerResource do
153
174
  @controller.instance_variable_get(:@project).should == :some_project
154
175
  end
155
176
 
156
- it "should not load through parent resource if instance isn't loaded" do
177
+ it "should load resource through the association of another parent resource using method" do
157
178
  @params.merge!(:action => "show", :id => 123)
158
- stub(Project).find(123) { :some_project }
179
+ category = Object.new
180
+ stub(@controller).category { category }
181
+ stub(category).projects.stub!.find(123) { :some_project }
159
182
  resource = CanCan::ControllerResource.new(@controller, :through => :category)
160
183
  resource.load_resource
161
184
  @controller.instance_variable_get(:@project).should == :some_project
162
185
  end
163
186
 
187
+ it "should not load through parent resource if instance isn't loaded when shallow" do
188
+ @params.merge!(:action => "show", :id => 123)
189
+ stub(Project).find(123) { :some_project }
190
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
191
+ resource.load_resource
192
+ @controller.instance_variable_get(:@project).should == :some_project
193
+ end
194
+
195
+ it "should raise AccessDenied when attempting to load resource through nil" do
196
+ @params.merge!(:action => "show", :id => 123)
197
+ stub(Project).find(123) { :some_project }
198
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
199
+ lambda {
200
+ resource.load_resource
201
+ }.should raise_error(CanCan::AccessDenied)
202
+ @controller.instance_variable_get(:@project).should be_nil
203
+ end
204
+
164
205
  it "should authorize nested resource through parent association on index action" do
165
206
  @params.merge!(:action => "index")
166
207
  category = Object.new
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::InheritedResource do
4
+ before(:each) do
5
+ @params = HashWithIndifferentAccess.new(:controller => "projects")
6
+ @controller = Object.new # simple stub for now
7
+ @ability = Ability.new(nil)
8
+ stub(@controller).params { @params }
9
+ stub(@controller).current_ability { @ability }
10
+ end
11
+
12
+ it "show should load resource through @controller.resource" do
13
+ @params[:action] = "show"
14
+ stub(@controller).resource { :project_resource }
15
+ CanCan::InheritedResource.new(@controller).load_resource
16
+ @controller.instance_variable_get(:@project).should == :project_resource
17
+ end
18
+
19
+ it "new should load through @controller.build_resource" do
20
+ @params[:action] = "new"
21
+ stub(@controller).build_resource { :project_resource }
22
+ CanCan::InheritedResource.new(@controller).load_resource
23
+ @controller.instance_variable_get(:@project).should == :project_resource
24
+ end
25
+
26
+ it "index should load through @controller.parent when parent" do
27
+ @params[:action] = "index"
28
+ stub(@controller).parent { :project_resource }
29
+ CanCan::InheritedResource.new(@controller, :parent => true).load_resource
30
+ @controller.instance_variable_get(:@project).should == :project_resource
31
+ end
32
+
33
+ it "index should load through @controller.end_of_association_chain" do
34
+ @params[:action] = "index"
35
+ stub(Project).accessible_by(@ability) { :projects }
36
+ stub(@controller).end_of_association_chain { Project }
37
+ CanCan::InheritedResource.new(@controller).load_resource
38
+ @controller.instance_variable_get(:@projects).should == :projects
39
+ end
40
+ end
@@ -1,4 +1,4 @@
1
- Spec::Matchers.define :orderlessly_match do |original_string|
1
+ RSpec::Matchers.define :orderlessly_match do |original_string|
2
2
  match do |given_string|
3
3
  original_string.split('').sort == given_string.split('').sort
4
4
  end
@@ -1,14 +1,11 @@
1
1
  require 'rubygems'
2
- require 'spec'
3
- require 'active_support'
4
- require 'active_record'
5
- require 'action_controller'
6
- require 'action_view'
2
+ require 'bundler'
3
+ Bundler.require(:default, :test)
4
+ require 'active_support/all'
7
5
  require 'matchers'
8
- require 'cancan'
9
6
  require 'cancan/matchers'
10
7
 
11
- Spec::Runner.configure do |config|
8
+ RSpec.configure do |config|
12
9
  config.mock_with :rr
13
10
  end
14
11
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ hash: 7
5
+ prerelease: false
5
6
  segments:
6
7
  - 1
7
8
  - 4
8
9
  - 0
9
- - beta1
10
- version: 1.4.0.beta1
10
+ version: 1.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Bates
@@ -15,10 +15,59 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-03 00:00:00 -07:00
18
+ date: 2010-10-05 00:00:00 -07:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 62196431
30
+ segments:
31
+ - 2
32
+ - 0
33
+ - 0
34
+ - beta
35
+ - 22
36
+ version: 2.0.0.beta.22
37
+ type: :development
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: rails
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ hash: 7
48
+ segments:
49
+ - 3
50
+ - 0
51
+ - 0
52
+ version: 3.0.0
53
+ type: :development
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: rr
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ hash: 33
64
+ segments:
65
+ - 0
66
+ - 10
67
+ - 11
68
+ version: 0.10.11
69
+ type: :development
70
+ version_requirements: *id003
22
71
  description: Simple authorization solution for Rails which is decoupled from user roles. All permissions are stored in a single location.
23
72
  email: ryan@railscasts.com
24
73
  executables: []
@@ -34,6 +83,7 @@ files:
34
83
  - lib/cancan/controller_additions.rb
35
84
  - lib/cancan/controller_resource.rb
36
85
  - lib/cancan/exceptions.rb
86
+ - lib/cancan/inherited_resource.rb
37
87
  - lib/cancan/matchers.rb
38
88
  - lib/cancan/query.rb
39
89
  - lib/cancan.rb
@@ -43,12 +93,14 @@ files:
43
93
  - spec/cancan/controller_additions_spec.rb
44
94
  - spec/cancan/controller_resource_spec.rb
45
95
  - spec/cancan/exceptions_spec.rb
96
+ - spec/cancan/inherited_resource_spec.rb
46
97
  - spec/cancan/matchers_spec.rb
47
98
  - spec/cancan/query_spec.rb
48
99
  - spec/matchers.rb
49
100
  - spec/spec.opts
50
101
  - spec/spec_helper.rb
51
102
  - CHANGELOG.rdoc
103
+ - Gemfile
52
104
  - LICENSE
53
105
  - Rakefile
54
106
  - README.rdoc
@@ -63,16 +115,20 @@ rdoc_options: []
63
115
  require_paths:
64
116
  - lib
65
117
  required_ruby_version: !ruby/object:Gem::Requirement
118
+ none: false
66
119
  requirements:
67
120
  - - ">="
68
121
  - !ruby/object:Gem::Version
122
+ hash: 3
69
123
  segments:
70
124
  - 0
71
125
  version: "0"
72
126
  required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
73
128
  requirements:
74
129
  - - ">="
75
130
  - !ruby/object:Gem::Version
131
+ hash: 19
76
132
  segments:
77
133
  - 1
78
134
  - 3
@@ -81,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
137
  requirements: []
82
138
 
83
139
  rubyforge_project: cancan
84
- rubygems_version: 1.3.6
140
+ rubygems_version: 1.3.7
85
141
  signing_key:
86
142
  specification_version: 3
87
143
  summary: Simple authorization solution for Rails.