Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 223 lines (138 sloc) 7.691 kB
6c6a57b @ryanb adding documentation placeholder
authored
1 = CanCan
2
4322da9 @ryanb expanding readme documentation
authored
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.
b1d3d66 @ryanb filling readme
authored
4
4322da9 @ryanb expanding readme documentation
authored
5 This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic]) which provides a current_user model.
b1d3d66 @ryanb filling readme
authored
6
7
8 == Installation
9
28eaf1b @ryanb releasing gem v0.1.0
authored
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.
b1d3d66 @ryanb filling readme
authored
15
9d58226 @ryanb couple fixes in readme
authored
16 sudo rake gems:install
b1d3d66 @ryanb filling readme
authored
17
28eaf1b @ryanb releasing gem v0.1.0
authored
18 Alternatively you can install it as a Rails plugin.
19
20 script/plugin install git://github.com/ryanb/cancan.git
b1d3d66 @ryanb filling readme
authored
21
22
23 == Setup
24
4322da9 @ryanb expanding readme documentation
authored
25 First, define a class called Ability in "models/ability.rb".
b1d3d66 @ryanb filling readme
authored
26
27 class Ability
28 include CanCan::Ability
29
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' t…
authored
30 def initialize(user)
b1d3d66 @ryanb filling readme
authored
31 if user.admin?
32 can :manage, :all
33 else
34 can :read, :all
35 end
36 end
37 end
38
4322da9 @ryanb expanding readme documentation
authored
39 This is where all permissions will go. See the "Defining Abilities" section below for more information.
b1d3d66 @ryanb filling readme
authored
40
4322da9 @ryanb expanding readme documentation
authored
41 You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
b1d3d66 @ryanb filling readme
authored
42
43 <% if can? :update, @article %>
44 <%= link_to "Edit", edit_article_path(@article) %>
45 <% end %>
46
4322da9 @ryanb expanding readme documentation
authored
47 You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
b1d3d66 @ryanb filling readme
authored
48
49 def show
50 @article = Article.find(params[:id])
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - clo…
authored
51 unauthorized! if cannot? :read, @article
b1d3d66 @ryanb filling readme
authored
52 end
53
4322da9 @ryanb expanding readme documentation
authored
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.
b1d3d66 @ryanb filling readme
authored
55
56 class ArticlesController < ApplicationController
57 before_filter :load_and_authorize_resource
58
59 def show
60 # @article is already loaded
61 end
62 end
63
4322da9 @ryanb expanding readme documentation
authored
64 If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the ApplicationController.
b1d3d66 @ryanb filling readme
authored
65
66 class ApplicationController < ActionController::Base
67 rescue_from CanCan::AccessDenied, :with => :access_denied
68
69 protected
70
71 def access_denied
72 flash[:error] = "Sorry, you are not allowed to access that page."
73 redirect_to root_url
74 end
75 end
76
77
78 == Defining Abilities
79
4322da9 @ryanb expanding readme documentation
authored
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.
b1d3d66 @ryanb filling readme
authored
81
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.
83
84 can :update, Article
85
86 You can pass an array for either of these parameters to match any one.
87
88 can [:update, :destroy], [Article, Comment]
89
90 In this case the user has the ability to update or destroy both articles and comments.
91
4322da9 @ryanb expanding readme documentation
authored
92 You can pass a block to provide logic based on the article's attributes.
b1d3d66 @ryanb filling readme
authored
93
94 can :update, Article do |article|
95 article && article.user == user
96 end
97
98 If the block returns true then the user has that :update ability for that article, otherwise he will be denied access. It's possible for the passed in model to be nil if one isn't specified, so be sure to take that into consideration.
99
100 You can pass :all to reference every type of object. In this case the object type will be passed into the block as well (just in case object is nil).
101
102 can :read, :all do |object_class, object|
103 object_class != Order
104 end
105
106 Here the user has permission to read all objects except orders.
107
108 You can also pass :manage as the action which will match any action. In this case the action is passed to the block.
109
110 can :manage, Comment do |action, comment|
111 action != :destroy
112 end
113
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
124
125
126 == Checking Abilities
127
128 Use the "can?" method in the controller or view to check the user's permission for a given action and object.
129
130 can? :destroy, @project
131
4322da9 @ryanb expanding readme documentation
authored
132 You can also pass the class instead of an instance (if you don't have one handy).
b1d3d66 @ryanb filling readme
authored
133
134 <% if can? :create, Project %>
135 <%= link_to "New Project", new_project_path %>
136 <% end %>
137
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - clo…
authored
138 The "cannot?" method is for convenience and performs the opposite check of "can?"
139
140 cannot? :destroy, @project
141
b1d3d66 @ryanb filling readme
authored
142
143 == Custom Actions
144
4322da9 @ryanb expanding readme documentation
authored
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.
b1d3d66 @ryanb filling readme
authored
146
147 # ability.rb
148 can :upload_picture, Project if user.pro?
149
150 # projects/_form.html.erb
151 <%= f.file_field :picture if can? :upload_picture, @project %>
152
153 # projects_controller.rb
154 def update
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - clo…
authored
155 unauthorized! if params[:project][:upload_picture] && cannot?(:upload_picture, @project)
b1d3d66 @ryanb filling readme
authored
156 # ...
157 end
158
159
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' t…
authored
160 == Assumptions & Configuring
b1d3d66 @ryanb filling readme
authored
161
162 CanCan makes two assumptions about your application.
163
4322da9 @ryanb expanding readme documentation
authored
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.
b1d3d66 @ryanb filling readme
authored
166
4322da9 @ryanb expanding readme documentation
authored
167 You can override these by overriding the "current_ability" method in your ApplicationController.
b1d3d66 @ryanb filling readme
authored
168
169 def current_ability
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' t…
authored
170 UserAbility.new(current_account) # instead of Ability.new(current_user)
b1d3d66 @ryanb filling readme
authored
171 end
172
173 That's it!
174
175
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
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' t…
authored
185 def initialize(user)
b1d3d66 @ryanb filling readme
authored
186 can :manage, :all do |action, object_class, object|
187 user.permissions.find_all_by_action(action).any? do |permission|
4322da9 @ryanb expanding readme documentation
authored
188 permission.object_type == object_class.to_s &&
b1d3d66 @ryanb filling readme
authored
189 (object.nil? || permission.object_id.nil? || permission.object_id == object.id)
190 end
191 end
192 end
193 end
194
4322da9 @ryanb expanding readme documentation
authored
195 An alternatie approach is to define a separate "can" ability for each permission.
196
197 def initialize(user)
072cb0f @ryanb fixing spacing issues in README
authored
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
4322da9 @ryanb expanding readme documentation
authored
204
b1d3d66 @ryanb filling readme
authored
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.
9d58226 @ryanb couple fixes in readme
authored
206
207
df27653 @ryanb adding documentation for testing abilities - closes #6
authored
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
072cb0f @ryanb fixing spacing issues in README
authored
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
df27653 @ryanb adding documentation for testing abilities - closes #6
authored
218
219
9d58226 @ryanb couple fixes in readme
authored
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.
Something went wrong with that request. Please try again.