fmalamitsas-cancan 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ 1.0.2 (Dec 30, 2009)
2
+
3
+ * Adding clear_aliased_actions to Ability which removes previously defined actions including defaults - see issue #20
4
+
5
+ * Append aliased actions (don't overwrite them) - see issue #20
6
+
7
+ * Adding custom message argument to unauthorized! method (thanks tjwallace) - see issue #18
8
+
9
+
10
+ 1.0.1 (Dec 14, 2009)
11
+
12
+ * Adding :class option to load_resource so one can customize which class to use for the model - see issue #17
13
+
14
+ * Don't fetch parent of nested resource if *_id parameter is missing so it works with shallow nested routes - see issue #14
15
+
16
+
17
+ 1.0.0 (Dec 13, 2009)
18
+
19
+ * Don't set resource instance variable if it has been set already - see issue #13
20
+
21
+ * Allowing :nested option to accept an array for deep nesting
22
+
23
+ * Adding :nested option to load resource method - see issue #10
24
+
25
+ * Pass :only and :except options to before filters for load/authorize resource methods.
26
+
27
+ * Adding :collection and :new options to load_resource method so we can specify behavior of additional actions if needed.
28
+
29
+ * BACKWARDS INCOMPATIBLE: turning load and authorize resource methods into class methods which set up the before filter so they can accept additional arguments.
30
+
31
+
32
+ 0.2.1 (Nov 26, 2009)
33
+
34
+ * many internal refactorings - see issues #11 and #12
35
+
36
+ * adding "cannot" method to define which abilities cannot be done - see issue #7
37
+
38
+ * support custom objects (usually symbols) in can definition - see issue #8
39
+
40
+
41
+ 0.2.0 (Nov 17, 2009)
42
+
43
+ * fix behavior of load_and_authorize_resource for namespaced controllers - see issue #3
44
+
45
+ * support arrays being passed to "can" to specify multiple actions or classes - see issue #2
46
+
47
+ * adding "cannot?" method to ability, controller, and view which is inverse of "can?" - see issue #1
48
+
49
+ * BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to set up abilities - see issue #4
50
+
51
+
52
+ 0.1.0 (Nov 16, 2009)
53
+
54
+ * initial release
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.
@@ -0,0 +1,203 @@
1
+ = CanCan
2
+
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] | Tests[http://runcoderun.com/ryanb/cancan]
4
+
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 for convenience.
6
+
7
+ This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic]) which provides a current_user model.
8
+
9
+ == Installation
10
+
11
+ You can set it up as a gem in your environment.rb file.
12
+
13
+ config.gem "cancan"
14
+
15
+ And then install the gem.
16
+
17
+ sudo rake gems:install
18
+
19
+ Alternatively you can install it as a Rails plugin.
20
+
21
+ script/plugin install git://github.com/ryanb/cancan.git
22
+
23
+
24
+ == Getting Started
25
+
26
+ First, define a class called Ability in "models/ability.rb".
27
+
28
+ class Ability
29
+ include CanCan::Ability
30
+
31
+ def initialize(user)
32
+ if user.admin?
33
+ can :manage, :all
34
+ else
35
+ can :read, :all
36
+ end
37
+ end
38
+ end
39
+
40
+ This is where all permissions will go. See the "Defining Abilities" section below for more information.
41
+
42
+ You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
43
+
44
+ <% if can? :update, @article %>
45
+ <%= link_to "Edit", edit_article_path(@article) %>
46
+ <% end %>
47
+
48
+ You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
49
+
50
+ def show
51
+ @article = Article.find(params[:id])
52
+ unauthorized! if cannot? :read, @article
53
+ end
54
+
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.
56
+
57
+ class ArticlesController < ApplicationController
58
+ load_and_authorize_resource
59
+
60
+ def show
61
+ # @article is already loaded
62
+ end
63
+ end
64
+
65
+ If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the ApplicationController.
66
+
67
+ class ApplicationController < ActionController::Base
68
+ rescue_from CanCan::AccessDenied do |exception|
69
+ flash[:error] = exception.message
70
+ redirect_to root_url
71
+ end
72
+ end
73
+
74
+
75
+ == Defining Abilities
76
+
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.
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
+
89
+ You can pass a block to provide logic based on the article's attributes.
90
+
91
+ can :update, Article do |article|
92
+ article && article.user == user
93
+ end
94
+
95
+ 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.
96
+
97
+ 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).
98
+
99
+ can :read, :all do |object_class, object|
100
+ object_class != Order
101
+ end
102
+
103
+ Here the user has permission to read all objects except orders.
104
+
105
+ You can also pass :manage as the action which will match any action. In this case the action is passed to the block.
106
+
107
+ can :manage, Comment do |action, comment|
108
+ action != :destroy
109
+ end
110
+
111
+ Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done.
112
+
113
+ can :read, :all
114
+ cannot :read, Product
115
+
116
+
117
+ == Checking Abilities
118
+
119
+ Use the "can?" method in the controller or view to check the user's permission for a given action and object.
120
+
121
+ can? :destroy, @project
122
+
123
+ You can also pass the class instead of an instance (if you don't have one handy).
124
+
125
+ <% if can? :create, Project %>
126
+ <%= link_to "New Project", new_project_path %>
127
+ <% end %>
128
+
129
+ The "cannot?" method is for convenience and performs the opposite check of "can?"
130
+
131
+ cannot? :destroy, @project
132
+
133
+
134
+ == Aliasing Actions
135
+
136
+ You can use the "alias_action" method to alias one or more actions into one.
137
+
138
+ alias_action :update, :destroy, :to => :modify
139
+ can :modify, Comment
140
+ can? :update, Comment # => true
141
+
142
+ The following aliases are added by default for conveniently mapping common controller actions.
143
+
144
+ alias_action :index, :show, :to => :read
145
+ alias_action :new, :to => :create
146
+ alias_action :edit, :to => :update
147
+
148
+
149
+ == Authorizing Controller Actions
150
+
151
+ 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.
152
+
153
+ load_and_authorize_resource :nested => :author
154
+
155
+ You can also pass an array to the :+nested+ attribute for deep nesting.
156
+
157
+ If you want to customize the loading behavior on certain actions, you can do so in a before filter.
158
+
159
+ class BooksController < ApplicationController
160
+ before_filter :find_book_by_permalink, :only => :show
161
+ load_and_authorize_resource
162
+
163
+ private
164
+
165
+ def find_book_by_permalink
166
+ @book = Book.find_by_permalink!(params[:id)
167
+ end
168
+ end
169
+
170
+ 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.
171
+
172
+
173
+ == Assumptions & Configuring
174
+
175
+ CanCan makes two assumptions about your application.
176
+
177
+ * You have an Ability class which defines the permissions.
178
+ * You have a current_user method in the controller which returns the current user model.
179
+
180
+ You can override these by overriding the "current_ability" method in your ApplicationController.
181
+
182
+ def current_ability
183
+ UserAbility.new(current_account) # instead of Ability.new(current_user)
184
+ end
185
+
186
+ That's it!
187
+
188
+
189
+ == Testing Abilities
190
+
191
+ 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.
192
+
193
+ def test "user can only destroy projects which he owns"
194
+ user = User.new
195
+ ability = Ability.new(user)
196
+ assert ability.can?(:destroy, Project.new(:user => user))
197
+ assert ability.cannot?(:destroy, Project.new)
198
+ end
199
+
200
+
201
+ == Special Thanks
202
+
203
+ 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.
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+
5
+ spec_files = Rake::FileList["spec/**/*_spec.rb"]
6
+
7
+ desc "Run specs"
8
+ Spec::Rake::SpecTask.new do |t|
9
+ t.spec_files = spec_files
10
+ t.spec_opts = ["-c"]
11
+ end
12
+
13
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'cancan'
@@ -0,0 +1,10 @@
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.
4
+ class AccessDenied < StandardError; end
5
+ end
6
+
7
+ require File.dirname(__FILE__) + '/cancan/ability'
8
+ require File.dirname(__FILE__) + '/cancan/controller_resource'
9
+ require File.dirname(__FILE__) + '/cancan/resource_authorization'
10
+ require File.dirname(__FILE__) + '/cancan/controller_additions'
@@ -0,0 +1,213 @@
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
+ #
18
+ module Ability
19
+ attr_accessor :user
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
50
+ end
51
+ end
52
+ false
53
+ end
54
+
55
+ # Convenience method which works the same as "can?" but returns the opposite value.
56
+ #
57
+ # cannot? :destroy, @project
58
+ #
59
+ def cannot?(*args)
60
+ !can?(*args)
61
+ end
62
+
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]
109
+ end
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]
126
+ end
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.
157
+ def alias_action(*args)
158
+ target = args.pop[:to]
159
+ aliased_actions[target] ||= []
160
+ aliased_actions[target] += args
161
+ end
162
+
163
+ # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
164
+ def aliased_actions
165
+ @aliased_actions ||= default_alias_actions
166
+ end
167
+
168
+ # Removes previously aliased actions including the defaults.
169
+ def clear_aliased_actions
170
+ @aliased_actions = {}
171
+ end
172
+
173
+ private
174
+
175
+ def default_alias_actions
176
+ {
177
+ :read => [:index, :show],
178
+ :create => [:new],
179
+ :update => [:edit],
180
+ }
181
+ end
182
+
183
+ def expand_actions(actions)
184
+ [actions].flatten.map do |action|
185
+ if aliased_actions[action]
186
+ [action, *aliased_actions[action]]
187
+ else
188
+ action
189
+ end
190
+ end.flatten
191
+ end
192
+
193
+ def can_perform_action?(action, noun, defined_actions, defined_nouns, defined_block)
194
+ if defined_block.nil?
195
+ true
196
+ else
197
+ block_args = []
198
+ block_args << action if defined_actions.include?(:manage)
199
+ block_args << (noun.class == Class ? noun : noun.class) if defined_nouns.include?(:all)
200
+ block_args << (noun.class == Class ? nil : noun)
201
+ return defined_block.call(*block_args)
202
+ end
203
+ end
204
+
205
+ def includes_action?(actions, action)
206
+ actions.include?(:manage) || actions.include?(action)
207
+ end
208
+
209
+ def includes_noun?(nouns, noun)
210
+ nouns.include?(:all) || nouns.include?(noun) || nouns.any? { |c| c.kind_of?(Class) && noun.kind_of?(c) }
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,186 @@
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.
5
+ module ControllerAdditions
6
+ module ClassMethods
7
+ # Sets up a before filter which loads and authorizes the current resource. This performs both
8
+ # load_resource and authorize_resource and accepts the same arguments. See those methods for details.
9
+ #
10
+ # class BooksController < ApplicationController
11
+ # load_and_authorize_resource
12
+ # end
13
+ #
14
+ def load_and_authorize_resource(options = {})
15
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_and_authorize_resource }
16
+ end
17
+
18
+ # Sets up a before filter which loads the appropriate model resource into an instance variable.
19
+ # For example, given an ArticlesController it will load the current article into the @article
20
+ # instance variable. It does this by either calling Article.find(params[:id]) or
21
+ # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
22
+ # action.
23
+ #
24
+ # Call this method directly on the controller class.
25
+ #
26
+ # class BooksController < ApplicationController
27
+ # load_resource
28
+ # end
29
+ #
30
+ # A resource is not loaded if the instance variable is already set. This makes it easy to override
31
+ # the behavior through a before_filter on certain actions.
32
+ #
33
+ # class BooksController < ApplicationController
34
+ # before_filter :find_book_by_permalink, :only => :show
35
+ # load_resource
36
+ #
37
+ # private
38
+ #
39
+ # def find_book_by_permalink
40
+ # @book = Book.find_by_permalink!(params[:id)
41
+ # end
42
+ # end
43
+ #
44
+ # See load_and_authorize_resource to automatically authorize the resource too.
45
+ #
46
+ # Options:
47
+ # [:+only+]
48
+ # Only applies before filter to given actions.
49
+ #
50
+ # [:+except+]
51
+ # Does not apply before filter to given actions.
52
+ #
53
+ # [:+nested+]
54
+ # Specify which resource this is nested under.
55
+ #
56
+ # load_resource :nested => :author
57
+ #
58
+ # Deep nesting can be defined in an array.
59
+ #
60
+ # load_resource :nested => [:publisher, :author]
61
+ #
62
+ # [:+class+]
63
+ # The class to use for the model.
64
+ #
65
+ # [:+collection+]
66
+ # Specify which actions are resource collection actions in addition to :+index+. This
67
+ # is usually not necessary because it will try to guess depending on if an :+id+
68
+ # is present in +params+.
69
+ #
70
+ # load_resource :collection => [:sort, :list]
71
+ #
72
+ # [:+new+]
73
+ # Specify which actions are new resource actions in addition to :+new+ and :+create+.
74
+ # Pass an action name into here if you would like to build a new resource instead of
75
+ # fetch one.
76
+ #
77
+ # load_resource :new => :build
78
+ #
79
+ def load_resource(options = {})
80
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_resource }
81
+ end
82
+
83
+ # Sets up a before filter which authorizes the current resource using the instance variable.
84
+ # For example, if you have an ArticlesController it will check the @article instance variable
85
+ # and ensure the user can perform the current action on it. Under the hood it is doing
86
+ # something like the following.
87
+ #
88
+ # unauthorized! if cannot?(params[:action].to_sym, @article || Article)
89
+ #
90
+ # Call this method directly on the controller class.
91
+ #
92
+ # class BooksController < ApplicationController
93
+ # authorize_resource
94
+ # end
95
+ #
96
+ # See load_and_authorize_resource to automatically load the resource too.
97
+ #
98
+ # Options:
99
+ # [:+only+]
100
+ # Only applies before filter to given actions.
101
+ #
102
+ # [:+except+]
103
+ # Does not apply before filter to given actions.
104
+ #
105
+ # [:+class+]
106
+ # The class to use for the model.
107
+ #
108
+ def authorize_resource(options = {})
109
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).authorize_resource }
110
+ end
111
+ end
112
+
113
+ def self.included(base)
114
+ base.extend ClassMethods
115
+ base.helper_method :can?, :cannot?
116
+ end
117
+
118
+ # Raises the CanCan::AccessDenied exception. This is often used in a
119
+ # controller action to mark a request as unauthorized.
120
+ #
121
+ # def show
122
+ # @article = Article.find(params[:id])
123
+ # unauthorized! if cannot? :read, @article
124
+ # end
125
+ #
126
+ # The unauthorized! method accepts an optional argument which sets the
127
+ # message of the exception.
128
+ #
129
+ # You can rescue from the exception in the controller to define the behavior.
130
+ #
131
+ # class ApplicationController < ActionController::Base
132
+ # rescue_from CanCan::AccessDenied do |exception|
133
+ # flash[:error] = exception.message
134
+ # redirect_to root_url
135
+ # end
136
+ # end
137
+ #
138
+ # See the load_and_authorize_resource method to automatically add
139
+ # the "unauthorized!" behavior to a RESTful controller's actions.
140
+ def unauthorized!(message = "You are not authorized to access this page.")
141
+ raise AccessDenied, message
142
+ end
143
+
144
+ # Creates and returns the current user's ability. You generally do not invoke
145
+ # this method directly, instead you can override this method to change its
146
+ # behavior if the Ability class or current_user method are different.
147
+ #
148
+ # def current_ability
149
+ # UserAbility.new(current_account) # instead of Ability.new(current_user)
150
+ # end
151
+ #
152
+ def current_ability
153
+ ::Ability.new(current_user)
154
+ end
155
+
156
+ # Use in the controller or view to check the user's permission for a given action
157
+ # and object.
158
+ #
159
+ # can? :destroy, @project
160
+ #
161
+ # You can also pass the class instead of an instance (if you don't have one handy).
162
+ #
163
+ # <% if can? :create, Project %>
164
+ # <%= link_to "New Project", new_project_path %>
165
+ # <% end %>
166
+ #
167
+ # This simply calls "can?" on the current_ability. See Ability#can?.
168
+ def can?(*args)
169
+ (@current_ability ||= current_ability).can?(*args)
170
+ end
171
+
172
+ # Convenience method which works the same as "can?" but returns the opposite value.
173
+ #
174
+ # cannot? :destroy, @project
175
+ #
176
+ def cannot?(*args)
177
+ (@current_ability ||= current_ability).cannot?(*args)
178
+ end
179
+ end
180
+ end
181
+
182
+ if defined? ActionController
183
+ ActionController::Base.class_eval do
184
+ include CanCan::ControllerAdditions
185
+ end
186
+ end
@@ -0,0 +1,40 @@
1
+ module CanCan
2
+ class ControllerResource # :nodoc:
3
+ def initialize(controller, name, parent = nil, options = {})
4
+ @controller = controller
5
+ @name = name
6
+ @parent = parent
7
+ @options = options
8
+ end
9
+
10
+ def model_class
11
+ @options[:class] || @name.to_s.camelize.constantize
12
+ end
13
+
14
+ def find(id)
15
+ self.model_instance ||= base.find(id)
16
+ end
17
+
18
+ def build(attributes)
19
+ if base.kind_of? Class
20
+ self.model_instance ||= base.new(attributes)
21
+ else
22
+ self.model_instance ||= base.build(attributes)
23
+ end
24
+ end
25
+
26
+ def model_instance
27
+ @controller.instance_variable_get("@#{@name}")
28
+ end
29
+
30
+ def model_instance=(instance)
31
+ @controller.instance_variable_set("@#{@name}", instance)
32
+ end
33
+
34
+ private
35
+
36
+ def base
37
+ @parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,64 @@
1
+ module CanCan
2
+ class ResourceAuthorization # :nodoc:
3
+ attr_reader :params
4
+
5
+ def initialize(controller, params, options = {})
6
+ @controller = controller
7
+ @params = params
8
+ @options = options
9
+ end
10
+
11
+ def load_and_authorize_resource
12
+ load_resource
13
+ authorize_resource
14
+ end
15
+
16
+ def load_resource
17
+ unless collection_actions.include? params[:action].to_sym
18
+ if new_actions.include? params[:action].to_sym
19
+ resource.build(params[model_name.to_sym])
20
+ elsif params[:id]
21
+ resource.find(params[:id])
22
+ end
23
+ end
24
+ end
25
+
26
+ def authorize_resource
27
+ if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
28
+ @options[:message] ? @controller.unauthorized!(@options[:message]) : @controller.unauthorized!
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def resource
35
+ @resource ||= ControllerResource.new(@controller, model_name, parent_resource, @options)
36
+ end
37
+
38
+ def parent_resource
39
+ parent = nil
40
+ [@options[:nested]].flatten.compact.each do |name|
41
+ id = @params["#{name}_id".to_sym]
42
+ if id
43
+ parent = ControllerResource.new(@controller, name, parent)
44
+ parent.find(id)
45
+ else
46
+ parent = nil
47
+ end
48
+ end
49
+ parent
50
+ end
51
+
52
+ def model_name
53
+ params[:controller].split('/').last.singularize
54
+ end
55
+
56
+ def collection_actions
57
+ [:index] + [@options[:collection]].flatten
58
+ end
59
+
60
+ def new_actions
61
+ [:new, :create] + [@options[:new]].flatten
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CanCan::Ability do
4
+ before(:each) do
5
+ @ability = Object.new
6
+ @ability.extend(CanCan::Ability)
7
+ end
8
+
9
+ it "should be able to :read anything" do
10
+ @ability.can :read, :all
11
+ @ability.can?(:read, String).should be_true
12
+ @ability.can?(:read, 123).should be_true
13
+ end
14
+
15
+ it "should not have permission to do something it doesn't know about" do
16
+ @ability.can?(:foodfight, String).should be_false
17
+ end
18
+
19
+ it "should return what block returns on a can call" do
20
+ @ability.can :read, :all
21
+ @ability.can :read, Symbol do |sym|
22
+ sym
23
+ end
24
+ @ability.can?(:read, Symbol).should be_nil
25
+ @ability.can?(:read, :some_symbol).should == :some_symbol
26
+ end
27
+
28
+ it "should pass class with object if :all objects are accepted" do
29
+ @ability.can :preview, :all do |object_class, object|
30
+ [object_class, object]
31
+ end
32
+ @ability.can?(:preview, 123).should == [Fixnum, 123]
33
+ end
34
+
35
+ it "should pass class with no object if :all objects are accepted and class is passed directly" do
36
+ @ability.can :preview, :all do |object_class, object|
37
+ [object_class, object]
38
+ end
39
+ @ability.can?(:preview, Hash).should == [Hash, nil]
40
+ end
41
+
42
+ it "should pass action and object for global manage actions" do
43
+ @ability.can :manage, Array do |action, object|
44
+ [action, object]
45
+ end
46
+ @ability.can?(:stuff, [1, 2]).should == [:stuff, [1, 2]]
47
+ @ability.can?(:stuff, Array).should == [:stuff, nil]
48
+ end
49
+
50
+ it "should alias update or destroy actions to modify action" do
51
+ @ability.alias_action :update, :destroy, :to => :modify
52
+ @ability.can(:modify, :all) { :modify_called }
53
+ @ability.can?(:update, 123).should == :modify_called
54
+ @ability.can?(:destroy, 123).should == :modify_called
55
+ end
56
+
57
+ it "should return block result for action, object_class, and object for any action" do
58
+ @ability.can :manage, :all do |action, object_class, object|
59
+ [action, object_class, object]
60
+ end
61
+ @ability.can?(:foo, 123).should == [:foo, Fixnum, 123]
62
+ @ability.can?(:bar, Fixnum).should == [:bar, Fixnum, nil]
63
+ end
64
+
65
+ it "should automatically alias index and show into read calls" do
66
+ @ability.can :read, :all
67
+ @ability.can?(:index, 123).should be_true
68
+ @ability.can?(:show, 123).should be_true
69
+ end
70
+
71
+ it "should automatically alias new and edit into create and update respectively" do
72
+ @ability.can(:create, :all) { :create_called }
73
+ @ability.can(:update, :all) { :update_called }
74
+ @ability.can?(:new, 123).should == :create_called
75
+ @ability.can?(:edit, 123).should == :update_called
76
+ end
77
+
78
+ it "should not respond to prepare (now using initialize)" do
79
+ @ability.should_not respond_to(:prepare)
80
+ end
81
+
82
+ it "should offer cannot? method which is simply invert of can?" do
83
+ @ability.cannot?(:tie, String).should be_true
84
+ end
85
+
86
+ it "should be able to specify multiple actions and match any" do
87
+ @ability.can [:read, :update], :all
88
+ @ability.can?(:read, 123).should be_true
89
+ @ability.can?(:update, 123).should be_true
90
+ @ability.can?(:count, 123).should be_false
91
+ end
92
+
93
+ it "should be able to specify multiple classes and match any" do
94
+ @ability.can :update, [String, Array]
95
+ @ability.can?(:update, "foo").should be_true
96
+ @ability.can?(:update, []).should be_true
97
+ @ability.can?(:update, 123).should be_false
98
+ end
99
+
100
+ it "should support custom objects in the can definition" do
101
+ @ability.can :read, :stats
102
+ @ability.can?(:read, :stats).should be_true
103
+ @ability.can?(:update, :stats).should be_false
104
+ @ability.can?(:read, :nonstats).should be_false
105
+ end
106
+
107
+ it "should support 'cannot' method to define what user cannot do" do
108
+ @ability.can :read, :all
109
+ @ability.cannot :read, Integer
110
+ @ability.can?(:read, "foo").should be_true
111
+ @ability.can?(:read, 123).should be_false
112
+ end
113
+
114
+ it "should support block on 'cannot' method" do
115
+ @ability.can :read, :all
116
+ @ability.cannot :read, Integer do |int|
117
+ int > 5
118
+ end
119
+ @ability.can?(:read, "foo").should be_true
120
+ @ability.can?(:read, 3).should be_true
121
+ @ability.can?(:read, 123).should be_false
122
+ end
123
+
124
+ it "should append aliased actions" do
125
+ @ability.alias_action :update, :to => :modify
126
+ @ability.alias_action :destroy, :to => :modify
127
+ @ability.aliased_actions[:modify].should == [:update, :destroy]
128
+ end
129
+
130
+ it "should clear aliased actions" do
131
+ @ability.alias_action :update, :to => :modify
132
+ @ability.clear_aliased_actions
133
+ @ability.aliased_actions[:modify].should be_nil
134
+ end
135
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CanCan::ControllerAdditions do
4
+ before(:each) do
5
+ @controller_class = Class.new
6
+ @controller = @controller_class.new
7
+ stub(@controller).params { {} }
8
+ mock(@controller_class).helper_method(:can?, :cannot?)
9
+ @controller_class.send(:include, CanCan::ControllerAdditions)
10
+ end
11
+
12
+ it "should raise access denied with default message when calling unauthorized!" do
13
+ lambda {
14
+ @controller.unauthorized!
15
+ }.should raise_error(CanCan::AccessDenied, "You are not authorized to access this page.")
16
+ end
17
+
18
+ it "should raise access denied with custom message when calling unauthorized!" do
19
+ lambda {
20
+ @controller.unauthorized! "Access denied!"
21
+ }.should raise_error(CanCan::AccessDenied, "Access denied!")
22
+ end
23
+
24
+ it "should have a current_ability method which generates an ability for the current user" do
25
+ stub(@controller).current_user { :current_user }
26
+ @controller.current_ability.should be_kind_of(Ability)
27
+ end
28
+
29
+ it "should provide a can? and cannot? methods which go through the current ability" do
30
+ stub(@controller).current_user { :current_user }
31
+ @controller.current_ability.should be_kind_of(Ability)
32
+ @controller.can?(:foo, :bar).should be_false
33
+ @controller.cannot?(:foo, :bar).should be_true
34
+ end
35
+
36
+ it "load_and_authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
37
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource
38
+ mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
39
+ @controller_class.load_and_authorize_resource :foo => :bar
40
+ end
41
+
42
+ it "authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
43
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource
44
+ mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
45
+ @controller_class.authorize_resource :foo => :bar, :except => :show
46
+ end
47
+
48
+ it "load_resource should setup a before filter which passes call to ResourceAuthorization" do
49
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_resource
50
+ mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) }
51
+ @controller_class.load_resource :foo => :bar, :only => [:show, :index]
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CanCan::ControllerResource do
4
+ before(:each) do
5
+ @controller = Object.new
6
+ end
7
+
8
+ it "should determine model class by constantizing give name" do
9
+ CanCan::ControllerResource.new(@controller, :ability).model_class.should == Ability
10
+ end
11
+
12
+ it "should fetch model through model class and assign it to the instance" do
13
+ stub(Ability).find(123) { :some_ability }
14
+ CanCan::ControllerResource.new(@controller, :ability).find(123)
15
+ @controller.instance_variable_get(:@ability).should == :some_ability
16
+ end
17
+
18
+ it "should fetch model through parent and assign it to the instance" do
19
+ parent = Object.new
20
+ stub(parent).model_instance.stub!.abilities.stub!.find(123) { :some_ability }
21
+ CanCan::ControllerResource.new(@controller, :ability, parent).find(123)
22
+ @controller.instance_variable_get(:@ability).should == :some_ability
23
+ end
24
+
25
+ it "should build model through model class and assign it to the instance" do
26
+ stub(Ability).new(123) { :some_ability }
27
+ CanCan::ControllerResource.new(@controller, :ability).build(123)
28
+ @controller.instance_variable_get(:@ability).should == :some_ability
29
+ end
30
+
31
+ it "should build model through parent and assign it to the instance" do
32
+ parent = Object.new
33
+ stub(parent).model_instance.stub!.abilities.stub!.build(123) { :some_ability }
34
+ CanCan::ControllerResource.new(@controller, :ability, parent).build(123)
35
+ @controller.instance_variable_get(:@ability).should == :some_ability
36
+ end
37
+
38
+ it "should not load resource if instance variable is already provided" do
39
+ @controller.instance_variable_set(:@ability, :some_ability)
40
+ CanCan::ControllerResource.new(@controller, :ability).find(123)
41
+ @controller.instance_variable_get(:@ability).should == :some_ability
42
+ end
43
+
44
+ it "should use the model class option if provided" do
45
+ stub(Person).find(123) { :some_resource }
46
+ CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person).find(123)
47
+ @controller.instance_variable_get(:@ability).should == :some_resource
48
+ end
49
+ end
@@ -0,0 +1,115 @@
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
+
60
+ it "should call load_resource and authorize_resource for load_and_authorize_resource" do
61
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
62
+ mock(authorization).load_resource
63
+ mock(authorization).authorize_resource
64
+ authorization.load_and_authorize_resource
65
+ end
66
+
67
+ it "should not build a resource when on custom collection action" do
68
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "sort"}, {:collection => [:sort, :list]})
69
+ authorization.load_resource
70
+ @controller.instance_variable_get(:@ability).should be_nil
71
+ end
72
+
73
+ it "should build a resource when on custom new action even when params[:id] exists" do
74
+ stub(Ability).new(nil) { :some_resource }
75
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "build", :id => 123}, {:new => :build})
76
+ authorization.load_resource
77
+ @controller.instance_variable_get(:@ability).should == :some_resource
78
+ end
79
+
80
+ it "should not try to load resource for other action if params[:id] is undefined" do
81
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "list")
82
+ authorization.load_resource
83
+ @controller.instance_variable_get(:@ability).should be_nil
84
+ end
85
+
86
+ it "should load nested resource and fetch other resource through the association" do
87
+ stub(Person).find(456).stub!.abilities.stub!.find(123) { :some_ability }
88
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123, :person_id => 456}, {:nested => :person})
89
+ authorization.load_resource
90
+ @controller.instance_variable_get(:@ability).should == :some_ability
91
+ end
92
+
93
+ it "should load nested resource and build resource through a deep association" do
94
+ stub(Person).find(456).stub!.behaviors.stub!.find(789).stub!.abilities.stub!.build(nil) { :some_ability }
95
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456, :behavior_id => 789}, {:nested => [:person, :behavior]})
96
+ authorization.load_resource
97
+ @controller.instance_variable_get(:@ability).should == :some_ability
98
+ end
99
+
100
+ it "should not load nested resource and build through this if *_id param isn't specified" do
101
+ stub(Person).find(456) { :some_person }
102
+ stub(Ability).new(nil) { :some_ability }
103
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456}, {:nested => [:person, :behavior]})
104
+ authorization.load_resource
105
+ @controller.instance_variable_get(:@person).should == :some_person
106
+ @controller.instance_variable_get(:@ability).should == :some_ability
107
+ end
108
+
109
+ it "should load the model using a custom class" do
110
+ stub(Person).find(123) { :some_resource }
111
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:class => Person})
112
+ authorization.load_resource
113
+ @controller.instance_variable_get(:@ability).should == :some_resource
114
+ end
115
+ end
@@ -0,0 +1,22 @@
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
12
+
13
+ class Ability
14
+ include CanCan::Ability
15
+
16
+ def initialize(user)
17
+ end
18
+ end
19
+
20
+ # this class helps out in testing nesting
21
+ class Person
22
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fmalamitsas-cancan
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Bates
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-03 00:00:00 +01: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/controller_resource.rb
30
+ - lib/cancan/resource_authorization.rb
31
+ - lib/cancan.rb
32
+ - spec/cancan/ability_spec.rb
33
+ - spec/cancan/controller_additions_spec.rb
34
+ - spec/cancan/controller_resource_spec.rb
35
+ - spec/cancan/resource_authorization_spec.rb
36
+ - spec/spec_helper.rb
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - CHANGELOG.rdoc
41
+ - init.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/ryanb/cancan
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --line-numbers
49
+ - --inline-source
50
+ - --title
51
+ - CanCan
52
+ - --main
53
+ - README.rdoc
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "1.2"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.5
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Simple authorization solution for Rails.(fork from Ryan Bates gem)
75
+ test_files: []
76
+