Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 212 lines (126 sloc) 7.622 kb
6c6a57b @ryanb adding documentation placeholder
authored
1 = CanCan
2
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
3 RDocs[http://rdoc.info/projects/ryanb/cancan] | Wiki[http://wiki.github.com/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan] | Metrics[http://getcaliper.com/caliper/project?repo=git%3A%2F%2Fgithub.com%2Fryanb%2Fcancan.git]
a13f78d @ryanb listing additional resources at top of readme page (including metrics an...
authored
4
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
5 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 and not duplicated across the controller, view, and database.
b1d3d66 @ryanb filling readme
authored
6
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
7 This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]) which provides a +current_user+ model.
b1d3d66 @ryanb filling readme
authored
8
9 == Installation
10
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
11 You can set CanCan up as a gem in your environment.rb file.
12
f7480d1 @ryanb releasing gem v1.0.0 (backwards incompatible, see changelog)
authored
13 config.gem "cancan"
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
14
28eaf1b @ryanb releasing gem v0.1.0
authored
15 And then install the gem.
b1d3d66 @ryanb filling readme
authored
16
9d58226 @ryanb couple fixes in readme
authored
17 sudo rake gems:install
b1d3d66 @ryanb filling readme
authored
18
28eaf1b @ryanb releasing gem v0.1.0
authored
19 Alternatively you can install it as a Rails plugin.
20
21 script/plugin install git://github.com/ryanb/cancan.git
b1d3d66 @ryanb filling readme
authored
22
23
f7480d1 @ryanb releasing gem v1.0.0 (backwards incompatible, see changelog)
authored
24 == Getting Started
b1d3d66 @ryanb filling readme
authored
25
4322da9 @ryanb expanding readme documentation
authored
26 First, define a class called Ability in "models/ability.rb".
b1d3d66 @ryanb filling readme
authored
27
28 class Ability
29 include CanCan::Ability
30
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to s...
authored
31 def initialize(user)
b1d3d66 @ryanb filling readme
authored
32 if user.admin?
33 can :manage, :all
34 else
35 can :read, :all
36 end
37 end
38 end
39
4322da9 @ryanb expanding readme documentation
authored
40 This is where all permissions will go. See the "Defining Abilities" section below for more information.
b1d3d66 @ryanb filling readme
authored
41
4322da9 @ryanb expanding readme documentation
authored
42 You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
b1d3d66 @ryanb filling readme
authored
43
44 <% if can? :update, @article %>
45 <%= link_to "Edit", edit_article_path(@article) %>
46 <% end %>
47
4322da9 @ryanb expanding readme documentation
authored
48 You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
b1d3d66 @ryanb filling readme
authored
49
50 def show
51 @article = Article.find(params[:id])
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - closes...
authored
52 unauthorized! if cannot? :read, @article
b1d3d66 @ryanb filling readme
authored
53 end
54
f7480d1 @ryanb releasing gem v1.0.0 (backwards incompatible, see changelog)
authored
55 Setting this for every action can be tedious, therefore the load_and_authorize_resource method is also provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it.
b1d3d66 @ryanb filling readme
authored
56
57 class ArticlesController < ApplicationController
f7480d1 @ryanb releasing gem v1.0.0 (backwards incompatible, see changelog)
authored
58 load_and_authorize_resource
b1d3d66 @ryanb filling readme
authored
59
60 def show
61 # @article is already loaded
62 end
63 end
64
4322da9 @ryanb expanding readme documentation
authored
65 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
66
67 class ApplicationController < ActionController::Base
f919ac5 @ryanb releasing gem v1.0.1
authored
68 rescue_from CanCan::AccessDenied do |exception|
ef22de6 @ryanb adding custom message argument to unauthorized! method - closes #18
authored
69 flash[:error] = exception.message
b1d3d66 @ryanb filling readme
authored
70 redirect_to root_url
71 end
72 end
73
74
75 == Defining Abilities
76
4322da9 @ryanb expanding readme documentation
authored
77 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
78
79 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.
80
81 can :update, Article
82
83 You can pass an array for either of these parameters to match any one.
84
85 can [:update, :destroy], [Article, Comment]
86
87 In this case the user has the ability to update or destroy both articles and comments.
88
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
89 You can pass a hash of conditions as the third argument.
b1d3d66 @ryanb filling readme
authored
90
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
91 can :read, Project, :active => true, :user_id => user.id
92
93 Here the user can only see active projects which he owns. See ControllerAdditions#conditions for a way to use this in database queries.
94
95 If the conditions hash does not give you enough control over defining abilities, you can use a block to write any Ruby code you want.
96
97 can :update, Project do |project|
98 project && project.groups.include?(user.group)
b1d3d66 @ryanb filling readme
authored
99 end
100
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#conditi...
authored
101 If the block returns true then the user has that :update ability for that project, 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.
102
103 The downside to using a block is that it cannot be used to generate conditions for database queries.
b1d3d66 @ryanb filling readme
authored
104
105 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).
106
107 can :read, :all do |object_class, object|
108 object_class != Order
109 end
110
111 Here the user has permission to read all objects except orders.
112
113 You can also pass :manage as the action which will match any action. In this case the action is passed to the block.
114
115 can :manage, Comment do |action, comment|
116 action != :destroy
117 end
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - closes #...
authored
118
119 Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done.
b1d3d66 @ryanb filling readme
authored
120
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - closes #...
authored
121 can :read, :all
122 cannot :read, Product
b1d3d66 @ryanb filling readme
authored
123
124
125 == Checking Abilities
126
127 Use the "can?" method in the controller or view to check the user's permission for a given action and object.
128
129 can? :destroy, @project
130
4322da9 @ryanb expanding readme documentation
authored
131 You can also pass the class instead of an instance (if you don't have one handy).
b1d3d66 @ryanb filling readme
authored
132
133 <% if can? :create, Project %>
134 <%= link_to "New Project", new_project_path %>
135 <% end %>
136
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - closes...
authored
137 The "cannot?" method is for convenience and performs the opposite check of "can?"
138
139 cannot? :destroy, @project
140
b1d3d66 @ryanb filling readme
authored
141
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - closes #...
authored
142 == Aliasing Actions
143
144 You can use the "alias_action" method to alias one or more actions into one.
145
146 alias_action :update, :destroy, :to => :modify
147 can :modify, Comment
148 can? :update, 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
f7480d1 @ryanb releasing gem v1.0.0 (backwards incompatible, see changelog)
authored
157 == Authorizing Controller Actions
158
159 As mentioned in the Getting Started section, you can use the +load_and_authorize_resource+ method in your controller to load the resource into an instance variable and authorize it. If you have a nested resource you can specify that as well.
160
161 load_and_authorize_resource :nested => :author
162
163 You can also pass an array to the :+nested+ attribute for deep nesting.
164
165 If you want to customize the loading behavior on certain actions, you can do so in a before filter.
166
167 class BooksController < ApplicationController
168 before_filter :find_book_by_permalink, :only => :show
169 load_and_authorize_resource
170
171 private
172
173 def find_book_by_permalink
174 @book = Book.find_by_permalink!(params[:id)
175 end
176 end
177
178 Here the @book instance variable is already set so it will not be loaded again for that action. This works for nested resources as well.
179
180
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to s...
authored
181 == Assumptions & Configuring
b1d3d66 @ryanb filling readme
authored
182
183 CanCan makes two assumptions about your application.
184
4322da9 @ryanb expanding readme documentation
authored
185 * You have an Ability class which defines the permissions.
186 * You have a current_user method in the controller which returns the current user model.
b1d3d66 @ryanb filling readme
authored
187
4322da9 @ryanb expanding readme documentation
authored
188 You can override these by overriding the "current_ability" method in your ApplicationController.
b1d3d66 @ryanb filling readme
authored
189
190 def current_ability
1edf583 @ryanb BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to s...
authored
191 UserAbility.new(current_account) # instead of Ability.new(current_user)
b1d3d66 @ryanb filling readme
authored
192 end
193
194 That's it!
195
196
df27653 @ryanb adding documentation for testing abilities - closes #6
authored
197 == Testing Abilities
198
199 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.
200
072cb0f @ryanb fixing spacing issues in README
authored
201 def test "user can only destroy projects which he owns"
202 user = User.new
203 ability = Ability.new(user)
204 assert ability.can?(:destroy, Project.new(:user => user))
205 assert ability.cannot?(:destroy, Project.new)
206 end
df27653 @ryanb adding documentation for testing abilities - closes #6
authored
207
208
9d58226 @ryanb couple fixes in readme
authored
209 == Special Thanks
210
211 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.