cancan 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,11 @@
1
+ 0.2.1 (Nov 26, 2009)
2
+
3
+ * many internal refactorings - see issues #11 and #12
4
+
5
+ * adding "cannot" method to define which abilities cannot be done - see issue #7
6
+
7
+ * support custom objects (usually symbols) in can definition - see issue #8
8
+
1
9
  0.2.0 (Nov 17, 2009)
2
10
 
3
11
  * fix behavior of load_and_authorize_resource for namespaced controllers - see issue #3
data/README.rdoc CHANGED
@@ -4,6 +4,7 @@ This is a simple authorization solution for Ruby on Rails to restrict what a giv
4
4
 
5
5
  This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic]) which provides a current_user model.
6
6
 
7
+ See the RDocs[http://rdoc.info/projects/ryanb/cancan] and Wiki[http://wiki.github.com/ryanb/cancan] for additional documentation.
7
8
 
8
9
  == Installation
9
10
 
@@ -110,17 +111,11 @@ You can also pass :manage as the action which will match any action. In this cas
110
111
  can :manage, Comment do |action, comment|
111
112
  action != :destroy
112
113
  end
114
+
115
+ Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done.
113
116
 
114
- Finally, you can use the "alias_action" method to alias one or more actions into one.
115
-
116
- alias_action :update, :destroy, :to => :modify
117
- can :modify, Comment
118
-
119
- The following aliases are added by default for conveniently mapping common controller actions.
120
-
121
- alias_action :index, :show, :to => :read
122
- alias_action :new, :to => :create
123
- alias_action :edit, :to => :update
117
+ can :read, :all
118
+ cannot :read, Product
124
119
 
125
120
 
126
121
  == Checking Abilities
@@ -140,21 +135,19 @@ The "cannot?" method is for convenience and performs the opposite check of "can?
140
135
  cannot? :destroy, @project
141
136
 
142
137
 
143
- == Custom Actions
138
+ == Aliasing Actions
144
139
 
145
- You can have fine grained control over abilities by coming up with new actions. For example, if only pro users are allowed to upload a picture for their product, you could add the following restrictions.
140
+ You can use the "alias_action" method to alias one or more actions into one.
146
141
 
147
- # ability.rb
148
- can :upload_picture, Project if user.pro?
142
+ alias_action :update, :destroy, :to => :modify
143
+ can :modify, Comment
144
+ can? :update, Comment # => true
149
145
 
150
- # projects/_form.html.erb
151
- <%= f.file_field :picture if can? :upload_picture, @project %>
152
-
153
- # projects_controller.rb
154
- def update
155
- unauthorized! if params[:project][:upload_picture] && cannot?(:upload_picture, @project)
156
- # ...
157
- end
146
+ The following aliases are added by default for conveniently mapping common controller actions.
147
+
148
+ alias_action :index, :show, :to => :read
149
+ alias_action :new, :to => :create
150
+ alias_action :edit, :to => :update
158
151
 
159
152
 
160
153
  == Assumptions & Configuring
@@ -173,48 +166,16 @@ You can override these by overriding the "current_ability" method in your Applic
173
166
  That's it!
174
167
 
175
168
 
176
- == Permissions in Database
177
-
178
- Perhaps a non-coder needs the ability to modify the user abilities, or you want to change them without having to re-deploy the application. In that case it may be best to store the permission logic in a separate model, let's call it Permission. It is easy to use the database records when defining abilities.
179
-
180
- For example, let's assume that each user has_many :permissions, and each permission has "action", "object_type" and "object_id" columns. The last of which is optional.
181
-
182
- class Ability
183
- include CanCan::Ability
184
-
185
- def initialize(user)
186
- can :manage, :all do |action, object_class, object|
187
- user.permissions.find_all_by_action(action).any? do |permission|
188
- permission.object_type == object_class.to_s &&
189
- (object.nil? || permission.object_id.nil? || permission.object_id == object.id)
190
- end
191
- end
192
- end
193
- end
194
-
195
- An alternatie approach is to define a separate "can" ability for each permission.
196
-
197
- def initialize(user)
198
- user.permissions.each do |permission|
199
- can permission.action, permission.object_type.constantize do |object|
200
- object.nil? || permission.object_id.nil? || permission.object_id == object.id
201
- end
202
- end
203
- end
204
-
205
- The actual details will depend largely on your application requirements, but hopefully you can see how it's possible to define permissions in the database and use them with CanCan.
206
-
207
-
208
169
  == Testing Abilities
209
170
 
210
171
  It is very easy to test the Ability model since you can call "can?" directly on it as you would in the view or controller.
211
172
 
212
- def test "user can only destroy projects which he owns"
213
- user = User.new
214
- ability = Ability.new(user)
215
- assert ability.can?(:destroy, Project.new(:user => user))
216
- assert ability.cannot?(:destroy, Project.new)
217
- end
173
+ def test "user can only destroy projects which he owns"
174
+ user = User.new
175
+ ability = Ability.new(user)
176
+ assert ability.can?(:destroy, Project.new(:user => user))
177
+ assert ability.cannot?(:destroy, Project.new)
178
+ end
218
179
 
219
180
 
220
181
  == Special Thanks
data/lib/cancan.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module CanCan
2
+ # This error is raised when a user isn't allowed to access a given
3
+ # controller action. See ControllerAdditions#unauthorized! for details.
2
4
  class AccessDenied < StandardError; end
3
5
  end
4
6
 
5
7
  require File.dirname(__FILE__) + '/cancan/ability'
8
+ require File.dirname(__FILE__) + '/cancan/resource_authorization'
6
9
  require File.dirname(__FILE__) + '/cancan/controller_additions'
@@ -1,49 +1,168 @@
1
1
  module CanCan
2
+
3
+ # This module is designed to be included into an Ability class. This will
4
+ # provide the "can" methods for defining and checking abilities.
5
+ #
6
+ # class Ability
7
+ # include CanCan::Ability
8
+ #
9
+ # def initialize(user)
10
+ # if user.admin?
11
+ # can :manage, :all
12
+ # else
13
+ # can :read, :all
14
+ # end
15
+ # end
16
+ # end
17
+ #
2
18
  module Ability
3
19
  attr_accessor :user
4
-
5
- def can?(original_action, target) # TODO this could use some refactoring
6
- (@can_history || []).reverse.each do |can_action, can_target, can_block|
7
- can_actions = [can_action].flatten
8
- can_targets = [can_target].flatten
9
- possible_actions_for(original_action).each do |action|
10
- if (can_actions.include?(:manage) || can_actions.include?(action)) && (can_targets.include?(:all) || can_targets.include?(target) || can_targets.any? { |c| target.kind_of?(c) })
11
- if can_block.nil?
12
- return true
13
- else
14
- block_args = []
15
- block_args << action if can_actions.include?(:manage)
16
- block_args << (target.class == Class ? target : target.class) if can_targets.include?(:all)
17
- block_args << (target.class == Class ? nil : target)
18
- return can_block.call(*block_args)
19
- end
20
- end
20
+
21
+ # Use to check the user's permission for a given action and object.
22
+ #
23
+ # can? :destroy, @project
24
+ #
25
+ # You can also pass the class instead of an instance (if you don't have one handy).
26
+ #
27
+ # can? :create, Project
28
+ #
29
+ # Not only can you use the can? method in the controller and view (see ControllerAdditions),
30
+ # but you can also call it directly on an ability instance.
31
+ #
32
+ # ability.can? :destroy, @project
33
+ #
34
+ # This makes testing a user's abilities very easy.
35
+ #
36
+ # def test "user can only destroy projects which he owns"
37
+ # user = User.new
38
+ # ability = Ability.new(user)
39
+ # assert ability.can?(:destroy, Project.new(:user => user))
40
+ # assert ability.cannot?(:destroy, Project.new)
41
+ # end
42
+ #
43
+ def can?(action, noun)
44
+ (@can_definitions || []).reverse.each do |base_behavior, defined_action, defined_noun, defined_block|
45
+ defined_actions = expand_actions(defined_action)
46
+ defined_nouns = [defined_noun].flatten
47
+ if includes_action?(defined_actions, action) && includes_noun?(defined_nouns, noun)
48
+ result = can_perform_action?(action, noun, defined_actions, defined_nouns, defined_block)
49
+ return base_behavior ? result : !result
21
50
  end
22
51
  end
23
52
  false
24
53
  end
25
54
 
55
+ # Convenience method which works the same as "can?" but returns the opposite value.
56
+ #
57
+ # cannot? :destroy, @project
58
+ #
26
59
  def cannot?(*args)
27
60
  !can?(*args)
28
61
  end
29
62
 
30
- def possible_actions_for(initial_action)
31
- actions = [initial_action]
32
- (@aliased_actions || default_alias_actions).each do |target, aliases|
33
- actions += possible_actions_for(target) if aliases.include? initial_action
34
- end
35
- actions
63
+ # Defines which abilities are allowed using two arguments. The first one is the action
64
+ # you're setting the permission for, the second one is the class of object you're setting it on.
65
+ #
66
+ # can :update, Article
67
+ #
68
+ # You can pass an array for either of these parameters to match any one.
69
+ #
70
+ # can [:update, :destroy], [Article, Comment]
71
+ #
72
+ # In this case the user has the ability to update or destroy both articles and comments.
73
+ #
74
+ # You can pass a block to provide logic based on the article's attributes.
75
+ #
76
+ # can :update, Article do |article|
77
+ # article && article.user == user
78
+ # end
79
+ #
80
+ # If the block returns true then the user has that :update ability for that article, otherwise he
81
+ # will be denied access. It's possible for the passed in model to be nil if one isn't specified,
82
+ # so be sure to take that into consideration.
83
+ #
84
+ # You can pass :all to reference every type of object. In this case the object type will be passed
85
+ # into the block as well (just in case object is nil).
86
+ #
87
+ # can :read, :all do |object_class, object|
88
+ # object_class != Order
89
+ # end
90
+ #
91
+ # Here the user has permission to read all objects except orders.
92
+ #
93
+ # You can also pass :manage as the action which will match any action. In this case the action is
94
+ # passed to the block.
95
+ #
96
+ # can :manage, Comment do |action, comment|
97
+ # action != :destroy
98
+ # end
99
+ #
100
+ # You can pass custom objects into this "can" method, this is usually done through a symbol
101
+ # and is useful if a class isn't available to define permissions on.
102
+ #
103
+ # can :read, :stats
104
+ # can? :read, :stats # => true
105
+ #
106
+ def can(action, noun, &block)
107
+ @can_definitions ||= []
108
+ @can_definitions << [true, action, noun, block]
36
109
  end
37
-
38
- def can(action, target, &block)
39
- @can_history ||= []
40
- @can_history << [action, target, block]
110
+
111
+ # Define an ability which cannot be done. Accepts the same arguments as "can".
112
+ #
113
+ # can :read, :all
114
+ # cannot :read, Comment
115
+ #
116
+ # A block can be passed just like "can", however if the logic is complex it is recommended
117
+ # to use the "can" method.
118
+ #
119
+ # cannot :read, Product do |product|
120
+ # product.invisible?
121
+ # end
122
+ #
123
+ def cannot(action, noun, &block)
124
+ @can_definitions ||= []
125
+ @can_definitions << [false, action, noun, block]
41
126
  end
42
127
 
128
+ # Alias one or more actions into another one.
129
+ #
130
+ # alias_action :update, :destroy, :to => :modify
131
+ # can :modify, Comment
132
+ #
133
+ # Then :modify permission will apply to both :update and :destroy requests.
134
+ #
135
+ # can? :update, Comment # => true
136
+ # can? :destroy, Comment # => true
137
+ #
138
+ # This only works in one direction. Passing the aliased action into the "can?" call
139
+ # will not work because aliases are meant to generate more generic actions.
140
+ #
141
+ # alias_action :update, :destroy, :to => :modify
142
+ # can :update, Comment
143
+ # can? :modify, Comment # => false
144
+ #
145
+ # Unless that exact alias is used.
146
+ #
147
+ # can :modify, Comment
148
+ # can? :modify, Comment # => true
149
+ #
150
+ # The following aliases are added by default for conveniently mapping common controller actions.
151
+ #
152
+ # alias_action :index, :show, :to => :read
153
+ # alias_action :new, :to => :create
154
+ # alias_action :edit, :to => :update
155
+ #
156
+ # This way one can use params[:action] in the controller to determine the permission.
43
157
  def alias_action(*args)
44
- @aliased_actions ||= default_alias_actions
45
158
  target = args.pop[:to]
46
- @aliased_actions[target] = args
159
+ aliased_actions[target] = args
160
+ end
161
+
162
+ private
163
+
164
+ def aliased_actions
165
+ @aliased_actions ||= default_alias_actions
47
166
  end
48
167
 
49
168
  def default_alias_actions
@@ -53,5 +172,35 @@ module CanCan
53
172
  :update => [:edit],
54
173
  }
55
174
  end
175
+
176
+ def expand_actions(actions)
177
+ [actions].flatten.map do |action|
178
+ if aliased_actions[action]
179
+ [action, *aliased_actions[action]]
180
+ else
181
+ action
182
+ end
183
+ end.flatten
184
+ end
185
+
186
+ def can_perform_action?(action, noun, defined_actions, defined_nouns, defined_block)
187
+ if defined_block.nil?
188
+ true
189
+ else
190
+ block_args = []
191
+ block_args << action if defined_actions.include?(:manage)
192
+ block_args << (noun.class == Class ? noun : noun.class) if defined_nouns.include?(:all)
193
+ block_args << (noun.class == Class ? nil : noun)
194
+ return defined_block.call(*block_args)
195
+ end
196
+ end
197
+
198
+ def includes_action?(actions, action)
199
+ actions.include?(:manage) || actions.include?(action)
200
+ end
201
+
202
+ def includes_noun?(nouns, noun)
203
+ nouns.include?(:all) || nouns.include?(noun) || nouns.any? { |c| c.kind_of?(Class) && noun.kind_of?(c) }
204
+ end
56
205
  end
57
206
  end
@@ -1,41 +1,113 @@
1
1
  module CanCan
2
+
3
+ # This module is automatically included into all controllers.
4
+ # It also makes the "can?" and "cannot?" methods available to all views.
2
5
  module ControllerAdditions
3
6
  def self.included(base)
4
7
  base.helper_method :can?, :cannot?
5
8
  end
6
9
 
10
+ # Raises the CanCan::AccessDenied exception. This is often used in a
11
+ # controller action to mark a request as unauthorized.
12
+ #
13
+ # def show
14
+ # @article = Article.find(params[:id])
15
+ # unauthorized! if cannot? :read, @article
16
+ # end
17
+ #
18
+ # You can rescue from the exception in the controller to specify
19
+ # the user experience.
20
+ #
21
+ # class ApplicationController < ActionController::Base
22
+ # rescue_from CanCan::AccessDenied, :with => :access_denied
23
+ #
24
+ # protected
25
+ #
26
+ # def access_denied
27
+ # flash[:error] = "Sorry, you are not allowed to access that page."
28
+ # redirect_to root_url
29
+ # end
30
+ # end
31
+ #
32
+ # See the load_and_authorize_resource method to automatically add
33
+ # the "unauthorized!" behavior to a RESTful controller's actions.
7
34
  def unauthorized!
8
35
  raise AccessDenied, "You are unable to access this page."
9
36
  end
10
37
 
38
+ # Creates and returns the current user's ability. You generally do not invoke
39
+ # this method directly, instead you can override this method to change its
40
+ # behavior if the Ability class or current_user method are different.
41
+ #
42
+ # def current_ability
43
+ # UserAbility.new(current_account) # instead of Ability.new(current_user)
44
+ # end
45
+ #
11
46
  def current_ability
12
47
  ::Ability.new(current_user)
13
48
  end
14
49
 
50
+ # Use in the controller or view to check the user's permission for a given action
51
+ # and object.
52
+ #
53
+ # can? :destroy, @project
54
+ #
55
+ # You can also pass the class instead of an instance (if you don't have one handy).
56
+ #
57
+ # <% if can? :create, Project %>
58
+ # <%= link_to "New Project", new_project_path %>
59
+ # <% end %>
60
+ #
61
+ # This simply calls "can?" on the current_ability. See Ability#can?.
15
62
  def can?(*args)
16
63
  (@current_ability ||= current_ability).can?(*args)
17
64
  end
18
65
 
66
+ # Convenience method which works the same as "can?" but returns the opposite value.
67
+ #
68
+ # cannot? :destroy, @project
69
+ #
19
70
  def cannot?(*args)
20
71
  (@current_ability ||= current_ability).cannot?(*args)
21
72
  end
22
73
 
23
- def load_resource # TODO this could use some refactoring
24
- model_name = params[:controller].split('/').last.singularize
25
- unless params[:action] == "index"
26
- if params[:id]
27
- instance_variable_set("@#{model_name}", model_name.camelcase.constantize.find(params[:id]))
28
- else
29
- instance_variable_set("@#{model_name}", model_name.camelcase.constantize.new(params[model_name.to_sym]))
30
- end
31
- end
74
+ # This method loads the appropriate model resource into an instance variable. For example,
75
+ # given an ArticlesController it will load the current article into the @article instance
76
+ # variable. It does this by either calling Article.find(params[:id]) or
77
+ # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
78
+ # action.
79
+ #
80
+ # You would often use this as a before filter in the controller. See
81
+ # load_and_authorize_resource to handle authorization too.
82
+ #
83
+ # before_filter :load_resource
84
+ #
85
+ def load_resource
86
+ ResourceAuthorization.new(self, params).load_resource
32
87
  end
33
88
 
34
- def authorize_resource # TODO this could use some refactoring
35
- model_name = params[:controller].split('/').last.singularize
36
- unauthorized! unless can?(params[:action].to_sym, instance_variable_get("@#{model_name}") || model_name.camelcase.constantize)
89
+ # Authorizes the resource in the current instance variable. For example,
90
+ # if you have an ArticlesController it will check the @article instance variable
91
+ # and ensure the user can perform the current action on it.
92
+ # Under the hood it is doing something like the following.
93
+ #
94
+ # unauthorized! if cannot?(params[:action].to_sym, @article || Article)
95
+ #
96
+ # You would often use this as a before filter in the controller.
97
+ #
98
+ # before_filter :authorize_resource
99
+ #
100
+ # See load_and_authorize_resource to automatically load the resource too.
101
+ def authorize_resource
102
+ ResourceAuthorization.new(self, params).authorize_resource
37
103
  end
38
104
 
105
+ # Calls load_resource to load the current resource model into an instance variable.
106
+ # Then calls authorize_resource to ensure the current user is authorized to access the page.
107
+ # You would often use this as a before filter in the controller.
108
+ #
109
+ # before_filter :load_and_authorize_resource
110
+ #
39
111
  def load_and_authorize_resource
40
112
  load_resource
41
113
  authorize_resource
@@ -0,0 +1,41 @@
1
+ module CanCan
2
+ class ResourceAuthorization # :nodoc:
3
+ attr_reader :params
4
+
5
+ def initialize(controller, params)
6
+ @controller = controller
7
+ @params = params
8
+ end
9
+
10
+ def load_and_authorize_resource
11
+ load_resource
12
+ authorize_resource
13
+ end
14
+
15
+ def load_resource
16
+ self.model_instance = params[:id] ? model_class.find(params[:id]) : model_class.new(params[model_name.to_sym]) unless params[:action] == "index"
17
+ end
18
+
19
+ def authorize_resource
20
+ @controller.unauthorized! if @controller.cannot?(params[:action].to_sym, model_instance || model_class)
21
+ end
22
+
23
+ private
24
+
25
+ def model_name
26
+ params[:controller].split('/').last.singularize
27
+ end
28
+
29
+ def model_class
30
+ model_name.camelcase.constantize
31
+ end
32
+
33
+ def model_instance
34
+ @controller.instance_variable_get("@#{model_name}")
35
+ end
36
+
37
+ def model_instance=(instance)
38
+ @controller.instance_variable_set("@#{model_name}", instance)
39
+ end
40
+ end
41
+ end
@@ -99,4 +99,28 @@ describe CanCan::Ability do
99
99
  @ability.can?(:update, []).should be_true
100
100
  @ability.can?(:update, 123).should be_false
101
101
  end
102
+
103
+ it "should support custom objects in the can definition" do
104
+ @ability.can :read, :stats
105
+ @ability.can?(:read, :stats).should be_true
106
+ @ability.can?(:update, :stats).should be_false
107
+ @ability.can?(:read, :nonstats).should be_false
108
+ end
109
+
110
+ it "should support 'cannot' method to define what user cannot do" do
111
+ @ability.can :read, :all
112
+ @ability.cannot :read, Integer
113
+ @ability.can?(:read, "foo").should be_true
114
+ @ability.can?(:read, 123).should be_false
115
+ end
116
+
117
+ it "should support block on 'cannot' method" do
118
+ @ability.can :read, :all
119
+ @ability.cannot :read, Integer do |int|
120
+ int > 5
121
+ end
122
+ @ability.can?(:read, "foo").should be_true
123
+ @ability.can?(:read, 3).should be_true
124
+ @ability.can?(:read, 123).should be_false
125
+ end
102
126
  end
@@ -1,16 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
- class Ability
4
- include CanCan::Ability
5
-
6
- def initialize(user)
7
- end
8
- end
9
-
10
3
  describe CanCan::ControllerAdditions do
11
4
  before(:each) do
12
5
  @controller_class = Class.new
13
6
  @controller = @controller_class.new
7
+ stub(@controller).params { {} }
14
8
  mock(@controller_class).helper_method(:can?, :cannot?)
15
9
  @controller_class.send(:include, CanCan::ControllerAdditions)
16
10
  end
@@ -33,60 +27,19 @@ describe CanCan::ControllerAdditions do
33
27
  @controller.cannot?(:foo, :bar).should be_true
34
28
  end
35
29
 
36
- it "should load the resource if params[:id] is specified" do
37
- stub(@controller).params { {:controller => "abilities", :action => "show", :id => 123} }
38
- stub(Ability).find(123) { :some_resource }
39
- @controller.load_resource
40
- @controller.instance_variable_get(:@ability).should == :some_resource
41
- end
42
-
43
- it "should build a new resource with hash if params[:id] is not specified" do
44
- stub(@controller).params { {:controller => "abilities", :action => "create", :ability => {:foo => "bar"}} }
45
- stub(Ability).new(:foo => "bar") { :some_resource }
46
- @controller.load_resource
47
- @controller.instance_variable_get(:@ability).should == :some_resource
48
- end
49
-
50
- it "should build a new resource even if attribute hash isn't specified" do
51
- stub(@controller).params { {:controller => "abilities", :action => "new"} }
52
- stub(Ability).new(nil) { :some_resource }
53
- @controller.load_resource
54
- @controller.instance_variable_get(:@ability).should == :some_resource
55
- end
56
-
57
- it "should not build a resource when on index action" do
58
- stub(@controller).params { {:controller => "abilities", :action => "index"} }
30
+ it "should load resource" do
31
+ mock.instance_of(CanCan::ResourceAuthorization).load_resource
59
32
  @controller.load_resource
60
- @controller.instance_variable_get(:@ability).should be_nil
61
33
  end
62
34
 
63
- it "should perform authorization using controller action and loaded model" do
64
- @controller.instance_variable_set(:@ability, :some_resource)
65
- stub(@controller).params { {:controller => "abilities", :action => "show"} }
66
- stub(@controller).can?(:show, :some_resource) { false }
67
- lambda {
68
- @controller.authorize_resource
69
- }.should raise_error(CanCan::AccessDenied)
35
+ it "should authorize resource" do
36
+ mock.instance_of(CanCan::ResourceAuthorization).authorize_resource
37
+ @controller.authorize_resource
70
38
  end
71
39
 
72
- it "should perform authorization using controller action and non loaded model" do
73
- stub(@controller).params { {:controller => "abilities", :action => "show"} }
74
- stub(@controller).can?(:show, Ability) { false }
75
- lambda {
76
- @controller.authorize_resource
77
- }.should raise_error(CanCan::AccessDenied)
78
- end
79
-
80
- it "should load and authorize resource in one call" do
40
+ it "should load and authorize resource in one call through controller" do
81
41
  mock(@controller).load_resource
82
- stub(@controller).authorize_resource
42
+ mock(@controller).authorize_resource
83
43
  @controller.load_and_authorize_resource
84
44
  end
85
-
86
- it "should properly load resource for namespaced controller" do
87
- stub(@controller).params { {:controller => "admin/abilities", :action => "show", :id => 123} }
88
- stub(Ability).find(123) { :some_resource }
89
- @controller.load_resource
90
- @controller.instance_variable_get(:@ability).should == :some_resource
91
- end
92
45
  end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CanCan::ResourceAuthorization do
4
+ before(:each) do
5
+ @controller = Object.new # simple stub for now
6
+ stub(@controller).unauthorized! { raise CanCan::AccessDenied }
7
+ end
8
+
9
+ it "should load the resource into an instance variable if params[:id] is specified" do
10
+ stub(Ability).find(123) { :some_resource }
11
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show", :id => 123)
12
+ authorization.load_resource
13
+ @controller.instance_variable_get(:@ability).should == :some_resource
14
+ end
15
+
16
+ it "should properly load resource for namespaced controller" do
17
+ stub(Ability).find(123) { :some_resource }
18
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "admin/abilities", :action => "show", :id => 123)
19
+ authorization.load_resource
20
+ @controller.instance_variable_get(:@ability).should == :some_resource
21
+ end
22
+
23
+ it "should build a new resource with hash if params[:id] is not specified" do
24
+ stub(Ability).new(:foo => "bar") { :some_resource }
25
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "create", :ability => {:foo => "bar"})
26
+ authorization.load_resource
27
+ @controller.instance_variable_get(:@ability).should == :some_resource
28
+ end
29
+
30
+ it "should build a new resource even if attribute hash isn't specified" do
31
+ stub(Ability).new(nil) { :some_resource }
32
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "new")
33
+ authorization.load_resource
34
+ @controller.instance_variable_get(:@ability).should == :some_resource
35
+ end
36
+
37
+ it "should not build a resource when on index action" do
38
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "index")
39
+ authorization.load_resource
40
+ @controller.instance_variable_get(:@ability).should be_nil
41
+ end
42
+
43
+ it "should perform authorization using controller action and loaded model" do
44
+ @controller.instance_variable_set(:@ability, :some_resource)
45
+ stub(@controller).cannot?(:show, :some_resource) { true }
46
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
47
+ lambda {
48
+ authorization.authorize_resource
49
+ }.should raise_error(CanCan::AccessDenied)
50
+ end
51
+
52
+ it "should perform authorization using controller action and non loaded model" do
53
+ stub(@controller).cannot?(:show, Ability) { true }
54
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
55
+ lambda {
56
+ authorization.authorize_resource
57
+ }.should raise_error(CanCan::AccessDenied)
58
+ end
59
+ end
data/spec/spec_helper.rb CHANGED
@@ -9,3 +9,10 @@ require File.dirname(__FILE__) + '/../lib/cancan.rb'
9
9
  Spec::Runner.configure do |config|
10
10
  config.mock_with :rr
11
11
  end
12
+
13
+ class Ability
14
+ include CanCan::Ability
15
+
16
+ def initialize(user)
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bates
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-17 00:00:00 -08:00
12
+ date: 2009-11-26 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,9 +26,11 @@ extra_rdoc_files:
26
26
  files:
27
27
  - lib/cancan/ability.rb
28
28
  - lib/cancan/controller_additions.rb
29
+ - lib/cancan/resource_authorization.rb
29
30
  - lib/cancan.rb
30
31
  - spec/cancan/ability_spec.rb
31
32
  - spec/cancan/controller_additions_spec.rb
33
+ - spec/cancan/resource_authorization_spec.rb
32
34
  - spec/spec_helper.rb
33
35
  - LICENSE
34
36
  - README.rdoc