cancan 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,14 @@
1
- *0.1.0* (Nov 16th, 2009)
1
+ 0.2.0 (Nov 17, 2009)
2
+
3
+ * fix behavior of load_and_authorize_resource for namespaced controllers - see issue #3
4
+
5
+ * support arrays being passed to "can" to specify multiple actions or classes - see issue #2
6
+
7
+ * adding "cannot?" method to ability, controller, and view which is inverse of "can?" - see issue #1
8
+
9
+ * BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to set up abilities - see issue #4
10
+
11
+
12
+ 0.1.0 (Nov 16, 2009)
2
13
 
3
14
  * initial release
@@ -1,27 +1,33 @@
1
1
  = CanCan
2
2
 
3
- This is a simple authorization solution for Rails which is completely decoupled from how you set up the user's roles. All permissions are stored in a single location for convenience.
3
+ This is a simple authorization solution for Ruby on Rails to restrict what a given user is allowed to access in the application. This is completely decoupled from any role based implementation allowing you to define user roles the way you want. All permissions are stored in a single location for convenience.
4
4
 
5
- This assumes you already have an authentication solution (such as Authlogic) which proves a current_user model.
5
+ This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic]) which provides a current_user model.
6
6
 
7
7
 
8
8
  == Installation
9
9
 
10
- Install it as a Rails plugin.
10
+ You can set it up as a gem in your environment.rb file.
11
+
12
+ config.gem "cancan", :source => "http://gemcutter.org"
13
+
14
+ And then install the gem.
11
15
 
12
- script/plugin install git://github.com/ryanb/cancan.git
16
+ sudo rake gems:install
17
+
18
+ Alternatively you can install it as a Rails plugin.
13
19
 
14
- It will be available as a gem soon.
20
+ script/plugin install git://github.com/ryanb/cancan.git
15
21
 
16
22
 
17
23
  == Setup
18
24
 
19
- First define a class called Ability, place it in "models/ability.rb".
25
+ First, define a class called Ability in "models/ability.rb".
20
26
 
21
27
  class Ability
22
28
  include CanCan::Ability
23
29
 
24
- def prepare(user)
30
+ def initialize(user)
25
31
  if user.admin?
26
32
  can :manage, :all
27
33
  else
@@ -30,22 +36,22 @@ First define a class called Ability, place it in "models/ability.rb".
30
36
  end
31
37
  end
32
38
 
33
- This class is where all permissions will go. See the "Defining Abilities" section below for more information.
39
+ This is where all permissions will go. See the "Defining Abilities" section below for more information.
34
40
 
35
- In the view layer you can access the current permissions at any point using the "can?" method. See "Checking Abilities" section below.
41
+ You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
36
42
 
37
43
  <% if can? :update, @article %>
38
44
  <%= link_to "Edit", edit_article_path(@article) %>
39
45
  <% end %>
40
46
 
41
- You can also use this method in the controller layer along with the "unauthorized!" method to restrict access.
47
+ You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
42
48
 
43
49
  def show
44
50
  @article = Article.find(params[:id])
45
- unauthorized! unless can? :read, @article
51
+ unauthorized! if cannot? :read, @article
46
52
  end
47
53
 
48
- Setting this for every action can be tedious, therefore a before filter is also provided for automatically applying this setting to a RESTful style resource controller.
54
+ Setting this for every action can be tedious, therefore a before filter is also provided to automatically authorize all actions in a RESTful style resource controller.
49
55
 
50
56
  class ArticlesController < ApplicationController
51
57
  before_filter :load_and_authorize_resource
@@ -55,7 +61,7 @@ Setting this for every action can be tedious, therefore a before filter is also
55
61
  end
56
62
  end
57
63
 
58
- If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior.
64
+ If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the ApplicationController.
59
65
 
60
66
  class ApplicationController < ActionController::Base
61
67
  rescue_from CanCan::AccessDenied, :with => :access_denied
@@ -71,7 +77,7 @@ If the user authorization fails, a CanCan::AccessDenied exception will be raised
71
77
 
72
78
  == Defining Abilities
73
79
 
74
- As shown above, the Ability#prepare method is where all user permissions are defined. The user model is passed into this method so you are free to modify the permissions based on the user's attributes. This way CanCan is completely decoupled with how you choose to handle roles.
80
+ As shown above, the Ability class is where all user permissions are defined. The user model is passed into the initialize method so you are free to modify the permissions based on the user's attributes. This way CanCan is completely decoupled with how you choose to handle roles.
75
81
 
76
82
  The "can" method accepts 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.
77
83
 
@@ -83,7 +89,7 @@ You can pass an array for either of these parameters to match any one.
83
89
 
84
90
  In this case the user has the ability to update or destroy both articles and comments.
85
91
 
86
- You can pass a block to provide logic based on the article's attributes. For example:
92
+ You can pass a block to provide logic based on the article's attributes.
87
93
 
88
94
  can :update, Article do |article|
89
95
  article && article.user == user
@@ -123,16 +129,20 @@ Use the "can?" method in the controller or view to check the user's permission f
123
129
 
124
130
  can? :destroy, @project
125
131
 
126
- You can also pass the class instead of an instance (if you don't have one handy). For example:
132
+ You can also pass the class instead of an instance (if you don't have one handy).
127
133
 
128
134
  <% if can? :create, Project %>
129
135
  <%= link_to "New Project", new_project_path %>
130
136
  <% end %>
131
137
 
138
+ The "cannot?" method is for convenience and performs the opposite check of "can?"
139
+
140
+ cannot? :destroy, @project
141
+
132
142
 
133
143
  == Custom Actions
134
144
 
135
- There is no limit to what actions you can use to determine abilities. For example, if only pro users are allowed to upload a picture for their product, you might add restrictions like this.
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.
136
146
 
137
147
  # ability.rb
138
148
  can :upload_picture, Project if user.pro?
@@ -142,24 +152,22 @@ There is no limit to what actions you can use to determine abilities. For exampl
142
152
 
143
153
  # projects_controller.rb
144
154
  def update
145
- unauthorized! if params[:project][:upload_picture] && !can?(:upload_picture, @project)
155
+ unauthorized! if params[:project][:upload_picture] && cannot?(:upload_picture, @project)
146
156
  # ...
147
157
  end
148
158
 
149
159
 
150
- == Customizing Assumptions
160
+ == Assumptions & Configuring
151
161
 
152
162
  CanCan makes two assumptions about your application.
153
163
 
154
- * The permissions are defined in Ability#prepare.
155
- * The user is fetched with current_user method in the controller.
164
+ * You have an Ability class which defines the permissions.
165
+ * You have a current_user method in the controller which returns the current user model.
156
166
 
157
- You can override these by defining the "current_ability" method in your ApplicationController.
167
+ You can override these by overriding the "current_ability" method in your ApplicationController.
158
168
 
159
169
  def current_ability
160
- ability = UserAbility.new # instead of Ability
161
- ability.prepare(current_account) # instead of current_user
162
- ability # be sure to return the ability
170
+ UserAbility.new(current_account) # instead of Ability.new(current_user)
163
171
  end
164
172
 
165
173
  That's it!
@@ -174,14 +182,41 @@ For example, let's assume that each user has_many :permissions, and each permiss
174
182
  class Ability
175
183
  include CanCan::Ability
176
184
 
177
- def prepare(user)
185
+ def initialize(user)
178
186
  can :manage, :all do |action, object_class, object|
179
187
  user.permissions.find_all_by_action(action).any? do |permission|
180
- permission.object_type.constantize == object_class &&
188
+ permission.object_type == object_class.to_s &&
181
189
  (object.nil? || permission.object_id.nil? || permission.object_id == object.id)
182
190
  end
183
191
  end
184
192
  end
185
193
  end
186
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
+
187
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
+ == Testing Abilities
209
+
210
+ 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
+
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
218
+
219
+
220
+ == Special Thanks
221
+
222
+ CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Many thanks to the authors and contributors.
@@ -4,14 +4,16 @@ module CanCan
4
4
 
5
5
  def can?(original_action, target) # TODO this could use some refactoring
6
6
  (@can_history || []).reverse.each do |can_action, can_target, can_block|
7
+ can_actions = [can_action].flatten
8
+ can_targets = [can_target].flatten
7
9
  possible_actions_for(original_action).each do |action|
8
- if (can_action == :manage || can_action == action) && (can_target == :all || can_target == target || target.kind_of?(can_target))
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) })
9
11
  if can_block.nil?
10
12
  return true
11
13
  else
12
14
  block_args = []
13
- block_args << action if can_action == :manage
14
- block_args << (target.class == Class ? target : target.class) if can_target == :all
15
+ block_args << action if can_actions.include?(:manage)
16
+ block_args << (target.class == Class ? target : target.class) if can_targets.include?(:all)
15
17
  block_args << (target.class == Class ? nil : target)
16
18
  return can_block.call(*block_args)
17
19
  end
@@ -21,6 +23,10 @@ module CanCan
21
23
  false
22
24
  end
23
25
 
26
+ def cannot?(*args)
27
+ !can?(*args)
28
+ end
29
+
24
30
  def possible_actions_for(initial_action)
25
31
  actions = [initial_action]
26
32
  (@aliased_actions || default_alias_actions).each do |target, aliases|
@@ -47,9 +53,5 @@ module CanCan
47
53
  :update => [:edit],
48
54
  }
49
55
  end
50
-
51
- def prepare(user)
52
- # to be overriden by included class
53
- end
54
56
  end
55
57
  end
@@ -1,7 +1,7 @@
1
1
  module CanCan
2
2
  module ControllerAdditions
3
3
  def self.included(base)
4
- base.helper_method :can?
4
+ base.helper_method :can?, :cannot?
5
5
  end
6
6
 
7
7
  def unauthorized!
@@ -9,27 +9,31 @@ module CanCan
9
9
  end
10
10
 
11
11
  def current_ability
12
- ability = ::Ability.new
13
- ability.prepare(current_user)
14
- ability
12
+ ::Ability.new(current_user)
15
13
  end
16
14
 
17
15
  def can?(*args)
18
16
  (@current_ability ||= current_ability).can?(*args)
19
17
  end
20
18
 
19
+ def cannot?(*args)
20
+ (@current_ability ||= current_ability).cannot?(*args)
21
+ end
22
+
21
23
  def load_resource # TODO this could use some refactoring
24
+ model_name = params[:controller].split('/').last.singularize
22
25
  unless params[:action] == "index"
23
26
  if params[:id]
24
- instance_variable_set("@#{params[:controller].singularize}", params[:controller].singularize.camelcase.constantize.find(params[:id]))
27
+ instance_variable_set("@#{model_name}", model_name.camelcase.constantize.find(params[:id]))
25
28
  else
26
- instance_variable_set("@#{params[:controller].singularize}", params[:controller].singularize.camelcase.constantize.new(params[params[:controller].singularize.to_sym]))
29
+ instance_variable_set("@#{model_name}", model_name.camelcase.constantize.new(params[model_name.to_sym]))
27
30
  end
28
31
  end
29
32
  end
30
33
 
31
34
  def authorize_resource # TODO this could use some refactoring
32
- unauthorized! unless can?(params[:action].to_sym, instance_variable_get("@#{params[:controller].singularize}") || params[:controller].singularize.camelcase.constantize)
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)
33
37
  end
34
38
 
35
39
  def load_and_authorize_resource
@@ -43,4 +47,4 @@ if defined? ActionController
43
47
  ActionController::Base.class_eval do
44
48
  include CanCan::ControllerAdditions
45
49
  end
46
- end
50
+ end
@@ -78,7 +78,25 @@ describe CanCan::Ability do
78
78
  @ability.can?(:edit, 123).should == :update_called
79
79
  end
80
80
 
81
- it "should respond to prepare" do
82
- @ability.should respond_to(:prepare)
81
+ it "should not respond to prepare (now using initialize)" do
82
+ @ability.should_not respond_to(:prepare)
83
+ end
84
+
85
+ it "should offer cannot? method which is simply invert of can?" do
86
+ @ability.cannot?(:tie, String).should be_true
87
+ end
88
+
89
+ it "should be able to specify multiple actions and match any" do
90
+ @ability.can [:read, :update], :all
91
+ @ability.can?(:read, 123).should be_true
92
+ @ability.can?(:update, 123).should be_true
93
+ @ability.can?(:count, 123).should be_false
94
+ end
95
+
96
+ it "should be able to specify multiple classes and match any" do
97
+ @ability.can :update, [String, Array]
98
+ @ability.can?(:update, "foo").should be_true
99
+ @ability.can?(:update, []).should be_true
100
+ @ability.can?(:update, 123).should be_false
83
101
  end
84
102
  end
@@ -2,13 +2,16 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  class Ability
4
4
  include CanCan::Ability
5
+
6
+ def initialize(user)
7
+ end
5
8
  end
6
9
 
7
10
  describe CanCan::ControllerAdditions do
8
11
  before(:each) do
9
12
  @controller_class = Class.new
10
13
  @controller = @controller_class.new
11
- mock(@controller_class).helper_method(:can?)
14
+ mock(@controller_class).helper_method(:can?, :cannot?)
12
15
  @controller_class.send(:include, CanCan::ControllerAdditions)
13
16
  end
14
17
 
@@ -23,10 +26,11 @@ describe CanCan::ControllerAdditions do
23
26
  @controller.current_ability.should be_kind_of(Ability)
24
27
  end
25
28
 
26
- it "should provide a can? method which goes through the current ability" do
29
+ it "should provide a can? and cannot? methods which go through the current ability" do
27
30
  stub(@controller).current_user { :current_user }
28
31
  @controller.current_ability.should be_kind_of(Ability)
29
32
  @controller.can?(:foo, :bar).should be_false
33
+ @controller.cannot?(:foo, :bar).should be_true
30
34
  end
31
35
 
32
36
  it "should load the resource if params[:id] is specified" do
@@ -78,4 +82,11 @@ describe CanCan::ControllerAdditions do
78
82
  stub(@controller).authorize_resource
79
83
  @controller.load_and_authorize_resource
80
84
  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
81
92
  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.1.0
4
+ version: 0.2.0
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-16 00:00:00 -08:00
12
+ date: 2009-11-17 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15