cancan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +187 -0
- data/Rakefile +11 -0
- data/init.rb +1 -0
- data/lib/cancan.rb +6 -0
- data/lib/cancan/ability.rb +55 -0
- data/lib/cancan/controller_additions.rb +46 -0
- data/spec/cancan/ability_spec.rb +84 -0
- data/spec/cancan/controller_additions_spec.rb +81 -0
- data/spec/spec_helper.rb +11 -0
- metadata +72 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Ryan Bates
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
= CanCan
|
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.
|
4
|
+
|
5
|
+
This assumes you already have an authentication solution (such as Authlogic) which proves a current_user model.
|
6
|
+
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
Install it as a Rails plugin.
|
11
|
+
|
12
|
+
script/plugin install git://github.com/ryanb/cancan.git
|
13
|
+
|
14
|
+
It will be available as a gem soon.
|
15
|
+
|
16
|
+
|
17
|
+
== Setup
|
18
|
+
|
19
|
+
First define a class called Ability, place it in "models/ability.rb".
|
20
|
+
|
21
|
+
class Ability
|
22
|
+
include CanCan::Ability
|
23
|
+
|
24
|
+
def prepare(user)
|
25
|
+
if user.admin?
|
26
|
+
can :manage, :all
|
27
|
+
else
|
28
|
+
can :read, :all
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
This class is where all permissions will go. See the "Defining Abilities" section below for more information.
|
34
|
+
|
35
|
+
In the view layer you can access the current permissions at any point using the "can?" method. See "Checking Abilities" section below.
|
36
|
+
|
37
|
+
<% if can? :update, @article %>
|
38
|
+
<%= link_to "Edit", edit_article_path(@article) %>
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
You can also use this method in the controller layer along with the "unauthorized!" method to restrict access.
|
42
|
+
|
43
|
+
def show
|
44
|
+
@article = Article.find(params[:id])
|
45
|
+
unauthorized! unless can? :read, @article
|
46
|
+
end
|
47
|
+
|
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.
|
49
|
+
|
50
|
+
class ArticlesController < ApplicationController
|
51
|
+
before_filter :load_and_authorize_resource
|
52
|
+
|
53
|
+
def show
|
54
|
+
# @article is already loaded
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior.
|
59
|
+
|
60
|
+
class ApplicationController < ActionController::Base
|
61
|
+
rescue_from CanCan::AccessDenied, :with => :access_denied
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def access_denied
|
66
|
+
flash[:error] = "Sorry, you are not allowed to access that page."
|
67
|
+
redirect_to root_url
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
== Defining Abilities
|
73
|
+
|
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.
|
75
|
+
|
76
|
+
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
|
+
|
78
|
+
can :update, Article
|
79
|
+
|
80
|
+
You can pass an array for either of these parameters to match any one.
|
81
|
+
|
82
|
+
can [:update, :destroy], [Article, Comment]
|
83
|
+
|
84
|
+
In this case the user has the ability to update or destroy both articles and comments.
|
85
|
+
|
86
|
+
You can pass a block to provide logic based on the article's attributes. For example:
|
87
|
+
|
88
|
+
can :update, Article do |article|
|
89
|
+
article && article.user == user
|
90
|
+
end
|
91
|
+
|
92
|
+
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.
|
93
|
+
|
94
|
+
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).
|
95
|
+
|
96
|
+
can :read, :all do |object_class, object|
|
97
|
+
object_class != Order
|
98
|
+
end
|
99
|
+
|
100
|
+
Here the user has permission to read all objects except orders.
|
101
|
+
|
102
|
+
You can also pass :manage as the action which will match any action. In this case the action is passed to the block.
|
103
|
+
|
104
|
+
can :manage, Comment do |action, comment|
|
105
|
+
action != :destroy
|
106
|
+
end
|
107
|
+
|
108
|
+
Finally, you can use the "alias_action" method to alias one or more actions into one.
|
109
|
+
|
110
|
+
alias_action :update, :destroy, :to => :modify
|
111
|
+
can :modify, Comment
|
112
|
+
|
113
|
+
The following aliases are added by default for conveniently mapping common controller actions.
|
114
|
+
|
115
|
+
alias_action :index, :show, :to => :read
|
116
|
+
alias_action :new, :to => :create
|
117
|
+
alias_action :edit, :to => :update
|
118
|
+
|
119
|
+
|
120
|
+
== Checking Abilities
|
121
|
+
|
122
|
+
Use the "can?" method in the controller or view to check the user's permission for a given action and object.
|
123
|
+
|
124
|
+
can? :destroy, @project
|
125
|
+
|
126
|
+
You can also pass the class instead of an instance (if you don't have one handy). For example:
|
127
|
+
|
128
|
+
<% if can? :create, Project %>
|
129
|
+
<%= link_to "New Project", new_project_path %>
|
130
|
+
<% end %>
|
131
|
+
|
132
|
+
|
133
|
+
== Custom Actions
|
134
|
+
|
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.
|
136
|
+
|
137
|
+
# ability.rb
|
138
|
+
can :upload_picture, Project if user.pro?
|
139
|
+
|
140
|
+
# projects/_form.html.erb
|
141
|
+
<%= f.file_field :picture if can? :upload_picture, @project %>
|
142
|
+
|
143
|
+
# projects_controller.rb
|
144
|
+
def update
|
145
|
+
unauthorized! if params[:project][:upload_picture] && !can?(:upload_picture, @project)
|
146
|
+
# ...
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
== Customizing Assumptions
|
151
|
+
|
152
|
+
CanCan makes two assumptions about your application.
|
153
|
+
|
154
|
+
* The permissions are defined in Ability#prepare.
|
155
|
+
* The user is fetched with current_user method in the controller.
|
156
|
+
|
157
|
+
You can override these by defining the "current_ability" method in your ApplicationController.
|
158
|
+
|
159
|
+
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
|
163
|
+
end
|
164
|
+
|
165
|
+
That's it!
|
166
|
+
|
167
|
+
|
168
|
+
== Permissions in Database
|
169
|
+
|
170
|
+
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.
|
171
|
+
|
172
|
+
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.
|
173
|
+
|
174
|
+
class Ability
|
175
|
+
include CanCan::Ability
|
176
|
+
|
177
|
+
def prepare(user)
|
178
|
+
can :manage, :all do |action, object_class, object|
|
179
|
+
user.permissions.find_all_by_action(action).any? do |permission|
|
180
|
+
permission.object_type.constantize == object_class &&
|
181
|
+
(object.nil? || permission.object_id.nil? || permission.object_id == object.id)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
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.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'cancan'
|
data/lib/cancan.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module CanCan
|
2
|
+
module Ability
|
3
|
+
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
|
+
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))
|
9
|
+
if can_block.nil?
|
10
|
+
return true
|
11
|
+
else
|
12
|
+
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 << (target.class == Class ? nil : target)
|
16
|
+
return can_block.call(*block_args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def possible_actions_for(initial_action)
|
25
|
+
actions = [initial_action]
|
26
|
+
(@aliased_actions || default_alias_actions).each do |target, aliases|
|
27
|
+
actions += possible_actions_for(target) if aliases.include? initial_action
|
28
|
+
end
|
29
|
+
actions
|
30
|
+
end
|
31
|
+
|
32
|
+
def can(action, target, &block)
|
33
|
+
@can_history ||= []
|
34
|
+
@can_history << [action, target, block]
|
35
|
+
end
|
36
|
+
|
37
|
+
def alias_action(*args)
|
38
|
+
@aliased_actions ||= default_alias_actions
|
39
|
+
target = args.pop[:to]
|
40
|
+
@aliased_actions[target] = args
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_alias_actions
|
44
|
+
{
|
45
|
+
:read => [:index, :show],
|
46
|
+
:create => [:new],
|
47
|
+
:update => [:edit],
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare(user)
|
52
|
+
# to be overriden by included class
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ControllerAdditions
|
3
|
+
def self.included(base)
|
4
|
+
base.helper_method :can?
|
5
|
+
end
|
6
|
+
|
7
|
+
def unauthorized!
|
8
|
+
raise AccessDenied, "You are unable to access this page."
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_ability
|
12
|
+
ability = ::Ability.new
|
13
|
+
ability.prepare(current_user)
|
14
|
+
ability
|
15
|
+
end
|
16
|
+
|
17
|
+
def can?(*args)
|
18
|
+
(@current_ability ||= current_ability).can?(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_resource # TODO this could use some refactoring
|
22
|
+
unless params[:action] == "index"
|
23
|
+
if params[:id]
|
24
|
+
instance_variable_set("@#{params[:controller].singularize}", params[:controller].singularize.camelcase.constantize.find(params[:id]))
|
25
|
+
else
|
26
|
+
instance_variable_set("@#{params[:controller].singularize}", params[:controller].singularize.camelcase.constantize.new(params[params[:controller].singularize.to_sym]))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
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)
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_and_authorize_resource
|
36
|
+
load_resource
|
37
|
+
authorize_resource
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if defined? ActionController
|
43
|
+
ActionController::Base.class_eval do
|
44
|
+
include CanCan::ControllerAdditions
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe CanCan::Ability do
|
4
|
+
before(:each) do
|
5
|
+
@ability_class = Class.new
|
6
|
+
@ability_class.send(:include, CanCan::Ability)
|
7
|
+
@ability = @ability_class.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be able to :read anything" do
|
11
|
+
@ability.can :read, :all
|
12
|
+
@ability.can?(:read, String).should be_true
|
13
|
+
@ability.can?(:read, 123).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not have permission to do something it doesn't know about" do
|
17
|
+
@ability.can?(:foodfight, String).should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return what block returns on a can call" do
|
21
|
+
@ability.can :read, :all
|
22
|
+
@ability.can :read, Symbol do |sym|
|
23
|
+
sym
|
24
|
+
end
|
25
|
+
@ability.can?(:read, Symbol).should be_nil
|
26
|
+
@ability.can?(:read, :some_symbol).should == :some_symbol
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should pass class with object if :all objects are accepted" do
|
30
|
+
@ability.can :preview, :all do |object_class, object|
|
31
|
+
[object_class, object]
|
32
|
+
end
|
33
|
+
@ability.can?(:preview, 123).should == [Fixnum, 123]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should pass class with no object if :all objects are accepted and class is passed directly" do
|
37
|
+
@ability.can :preview, :all do |object_class, object|
|
38
|
+
[object_class, object]
|
39
|
+
end
|
40
|
+
@ability.can?(:preview, Hash).should == [Hash, nil]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should pass action and object for global manage actions" do
|
44
|
+
@ability.can :manage, Array do |action, object|
|
45
|
+
[action, object]
|
46
|
+
end
|
47
|
+
@ability.can?(:stuff, [1, 2]).should == [:stuff, [1, 2]]
|
48
|
+
@ability.can?(:stuff, Array).should == [:stuff, nil]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should alias update or destroy actions to modify action" do
|
52
|
+
@ability.alias_action :update, :destroy, :to => :modify
|
53
|
+
@ability.can :modify, :all do |object_class, object|
|
54
|
+
:modify_called
|
55
|
+
end
|
56
|
+
@ability.can?(:update, 123).should == :modify_called
|
57
|
+
@ability.can?(:destroy, 123).should == :modify_called
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return block result for action, object_class, and object for any action" do
|
61
|
+
@ability.can :manage, :all do |action, object_class, object|
|
62
|
+
[action, object_class, object]
|
63
|
+
end
|
64
|
+
@ability.can?(:foo, 123).should == [:foo, Fixnum, 123]
|
65
|
+
@ability.can?(:bar, Fixnum).should == [:bar, Fixnum, nil]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should automatically alias index and show into read calls" do
|
69
|
+
@ability.can :read, :all
|
70
|
+
@ability.can?(:index, 123).should be_true
|
71
|
+
@ability.can?(:show, 123).should be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should automatically alias new and edit into create and update respectively" do
|
75
|
+
@ability.can(:create, :all) { :create_called }
|
76
|
+
@ability.can(:update, :all) { :update_called }
|
77
|
+
@ability.can?(:new, 123).should == :create_called
|
78
|
+
@ability.can?(:edit, 123).should == :update_called
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should respond to prepare" do
|
82
|
+
@ability.should respond_to(:prepare)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Ability
|
4
|
+
include CanCan::Ability
|
5
|
+
end
|
6
|
+
|
7
|
+
describe CanCan::ControllerAdditions do
|
8
|
+
before(:each) do
|
9
|
+
@controller_class = Class.new
|
10
|
+
@controller = @controller_class.new
|
11
|
+
mock(@controller_class).helper_method(:can?)
|
12
|
+
@controller_class.send(:include, CanCan::ControllerAdditions)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should read from the cache with request uri as key and render that text" do
|
16
|
+
lambda {
|
17
|
+
@controller.unauthorized!
|
18
|
+
}.should raise_error(CanCan::AccessDenied)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have a current_ability method which generates an ability for the current user" do
|
22
|
+
stub(@controller).current_user { :current_user }
|
23
|
+
@controller.current_ability.should be_kind_of(Ability)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should provide a can? method which goes through the current ability" do
|
27
|
+
stub(@controller).current_user { :current_user }
|
28
|
+
@controller.current_ability.should be_kind_of(Ability)
|
29
|
+
@controller.can?(:foo, :bar).should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should load the resource if params[:id] is specified" do
|
33
|
+
stub(@controller).params { {:controller => "abilities", :action => "show", :id => 123} }
|
34
|
+
stub(Ability).find(123) { :some_resource }
|
35
|
+
@controller.load_resource
|
36
|
+
@controller.instance_variable_get(:@ability).should == :some_resource
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should build a new resource with hash if params[:id] is not specified" do
|
40
|
+
stub(@controller).params { {:controller => "abilities", :action => "create", :ability => {:foo => "bar"}} }
|
41
|
+
stub(Ability).new(:foo => "bar") { :some_resource }
|
42
|
+
@controller.load_resource
|
43
|
+
@controller.instance_variable_get(:@ability).should == :some_resource
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should build a new resource even if attribute hash isn't specified" do
|
47
|
+
stub(@controller).params { {:controller => "abilities", :action => "new"} }
|
48
|
+
stub(Ability).new(nil) { :some_resource }
|
49
|
+
@controller.load_resource
|
50
|
+
@controller.instance_variable_get(:@ability).should == :some_resource
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not build a resource when on index action" do
|
54
|
+
stub(@controller).params { {:controller => "abilities", :action => "index"} }
|
55
|
+
@controller.load_resource
|
56
|
+
@controller.instance_variable_get(:@ability).should be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should perform authorization using controller action and loaded model" do
|
60
|
+
@controller.instance_variable_set(:@ability, :some_resource)
|
61
|
+
stub(@controller).params { {:controller => "abilities", :action => "show"} }
|
62
|
+
stub(@controller).can?(:show, :some_resource) { false }
|
63
|
+
lambda {
|
64
|
+
@controller.authorize_resource
|
65
|
+
}.should raise_error(CanCan::AccessDenied)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should perform authorization using controller action and non loaded model" do
|
69
|
+
stub(@controller).params { {:controller => "abilities", :action => "show"} }
|
70
|
+
stub(@controller).can?(:show, Ability) { false }
|
71
|
+
lambda {
|
72
|
+
@controller.authorize_resource
|
73
|
+
}.should raise_error(CanCan::AccessDenied)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should load and authorize resource in one call" do
|
77
|
+
mock(@controller).load_resource
|
78
|
+
stub(@controller).authorize_resource
|
79
|
+
@controller.load_and_authorize_resource
|
80
|
+
end
|
81
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'action_view'
|
7
|
+
require File.dirname(__FILE__) + '/../lib/cancan.rb'
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
config.mock_with :rr
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cancan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Bates
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-16 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Simple authorization solution for Rails which is completely decoupled from the user's roles. All permissions are stored in a single location for convenience.
|
17
|
+
email: ryan@railscasts.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- CHANGELOG.rdoc
|
25
|
+
- LICENSE
|
26
|
+
files:
|
27
|
+
- lib/cancan/ability.rb
|
28
|
+
- lib/cancan/controller_additions.rb
|
29
|
+
- lib/cancan.rb
|
30
|
+
- spec/cancan/ability_spec.rb
|
31
|
+
- spec/cancan/controller_additions_spec.rb
|
32
|
+
- spec/spec_helper.rb
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- CHANGELOG.rdoc
|
37
|
+
- init.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/ryanb/cancan
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --line-numbers
|
45
|
+
- --inline-source
|
46
|
+
- --title
|
47
|
+
- CanCan
|
48
|
+
- --main
|
49
|
+
- README.rdoc
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "1.2"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Simple authorization solution for Rails.
|
71
|
+
test_files: []
|
72
|
+
|