cancan 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,35 @@
1
1
  module CanCan
2
-
2
+
3
3
  # This module is automatically included into all controllers.
4
4
  # It also makes the "can?" and "cannot?" methods available to all views.
5
5
  module ControllerAdditions
6
6
  module ClassMethods
7
7
  # Sets up a before filter which loads and authorizes the current resource. This performs both
8
8
  # load_resource and authorize_resource and accepts the same arguments. See those methods for details.
9
- #
9
+ #
10
10
  # class BooksController < ApplicationController
11
11
  # load_and_authorize_resource
12
12
  # end
13
- #
13
+ #
14
14
  def load_and_authorize_resource(options = {})
15
15
  ResourceAuthorization.add_before_filter(self, :load_and_authorize_resource, options)
16
16
  end
17
-
17
+
18
18
  # Sets up a before filter which loads the appropriate model resource into an instance variable.
19
19
  # For example, given an ArticlesController it will load the current article into the @article
20
20
  # instance variable. It does this by either calling Article.find(params[:id]) or
21
21
  # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
22
22
  # action.
23
- #
23
+ #
24
24
  # Call this method directly on the controller class.
25
- #
25
+ #
26
26
  # class BooksController < ApplicationController
27
27
  # load_resource
28
28
  # end
29
- #
29
+ #
30
30
  # A resource is not loaded if the instance variable is already set. This makes it easy to override
31
31
  # the behavior through a before_filter on certain actions.
32
- #
32
+ #
33
33
  # class BooksController < ApplicationController
34
34
  # before_filter :find_book_by_permalink, :only => :show
35
35
  # load_resource
@@ -40,107 +40,117 @@ module CanCan
40
40
  # @book = Book.find_by_permalink!(params[:id)
41
41
  # end
42
42
  # end
43
- #
43
+ #
44
44
  # See load_and_authorize_resource to automatically authorize the resource too.
45
- #
45
+ #
46
46
  # Options:
47
47
  # [:+only+]
48
48
  # Only applies before filter to given actions.
49
- #
49
+ #
50
50
  # [:+except+]
51
51
  # Does not apply before filter to given actions.
52
- #
52
+ #
53
53
  # [:+nested+]
54
54
  # Specify which resource this is nested under.
55
- #
55
+ #
56
56
  # load_resource :nested => :author
57
- #
57
+ #
58
58
  # Deep nesting can be defined in an array.
59
- #
59
+ #
60
60
  # load_resource :nested => [:publisher, :author]
61
- #
61
+ #
62
+ # [:+name+]
63
+ # The name of the resource if it cannot be determined from controller (string or symbol).
64
+ #
65
+ # load_resource :name => :article
66
+ #
62
67
  # [:+resource+]
63
68
  # The class to use for the model (string or constant).
64
- #
69
+ #
65
70
  # [:+collection+]
66
71
  # Specify which actions are resource collection actions in addition to :+index+. This
67
72
  # is usually not necessary because it will try to guess depending on if an :+id+
68
73
  # is present in +params+.
69
- #
74
+ #
70
75
  # load_resource :collection => [:sort, :list]
71
- #
76
+ #
72
77
  # [:+new+]
73
78
  # Specify which actions are new resource actions in addition to :+new+ and :+create+.
74
79
  # Pass an action name into here if you would like to build a new resource instead of
75
80
  # fetch one.
76
- #
81
+ #
77
82
  # load_resource :new => :build
78
- #
83
+ #
79
84
  def load_resource(options = {})
80
85
  ResourceAuthorization.add_before_filter(self, :load_resource, options)
81
86
  end
82
-
87
+
83
88
  # Sets up a before filter which authorizes the current resource using the instance variable.
84
89
  # For example, if you have an ArticlesController it will check the @article instance variable
85
90
  # and ensure the user can perform the current action on it. Under the hood it is doing
86
91
  # something like the following.
87
- #
92
+ #
88
93
  # authorize!(params[:action].to_sym, @article || Article)
89
- #
94
+ #
90
95
  # Call this method directly on the controller class.
91
- #
96
+ #
92
97
  # class BooksController < ApplicationController
93
98
  # authorize_resource
94
99
  # end
95
- #
100
+ #
96
101
  # See load_and_authorize_resource to automatically load the resource too.
97
- #
102
+ #
98
103
  # Options:
99
104
  # [:+only+]
100
105
  # Only applies before filter to given actions.
101
- #
106
+ #
102
107
  # [:+except+]
103
108
  # Does not apply before filter to given actions.
104
- #
109
+ #
110
+ # [:+name+]
111
+ # The name of the resource if it cannot be determined from controller (string or symbol).
112
+ #
113
+ # load_resource :name => :article
114
+ #
105
115
  # [:+resource+]
106
116
  # The class to use for the model (string or constant). Alternatively pass a symbol
107
117
  # to represent a resource which does not have a class.
108
- #
118
+ #
109
119
  def authorize_resource(options = {})
110
120
  ResourceAuthorization.add_before_filter(self, :authorize_resource, options)
111
121
  end
112
122
  end
113
-
123
+
114
124
  def self.included(base)
115
125
  base.extend ClassMethods
116
126
  base.helper_method :can?, :cannot?
117
127
  end
118
-
128
+
119
129
  # Raises a CanCan::AccessDenied exception if the current_ability cannot
120
130
  # perform the given action. This is usually called in a controller action or
121
131
  # before filter to perform the authorization.
122
- #
132
+ #
123
133
  # def show
124
134
  # @article = Article.find(params[:id])
125
135
  # authorize! :read, @article
126
136
  # end
127
- #
137
+ #
128
138
  # A :message option can be passed to specify a different message.
129
- #
139
+ #
130
140
  # authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
131
- #
141
+ #
132
142
  # You can rescue from the exception in the controller to customize how unauthorized
133
143
  # access is displayed to the user.
134
- #
144
+ #
135
145
  # class ApplicationController < ActionController::Base
136
146
  # rescue_from CanCan::AccessDenied do |exception|
137
147
  # flash[:error] = exception.message
138
148
  # redirect_to root_url
139
149
  # end
140
150
  # end
141
- #
151
+ #
142
152
  # See the CanCan::AccessDenied exception for more details on working with the exception.
143
- #
153
+ #
144
154
  # See the load_and_authorize_resource method to automatically add the authorize! behavior
145
155
  # to the default RESTful actions.
146
156
  def authorize!(action, subject, *args)
@@ -150,46 +160,46 @@ module CanCan
150
160
  end
151
161
  raise AccessDenied.new(message, action, subject) if cannot?(action, subject, *args)
152
162
  end
153
-
163
+
154
164
  def unauthorized!(message = nil)
155
165
  raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
156
166
  end
157
-
167
+
158
168
  # Creates and returns the current user's ability and caches it. If you
159
169
  # want to override how the Ability is defined then this is the place.
160
170
  # Just define the method in the controller to change behavior.
161
- #
171
+ #
162
172
  # def current_ability
163
173
  # # instead of Ability.new(current_user)
164
174
  # @current_ability ||= UserAbility.new(current_account)
165
175
  # end
166
- #
176
+ #
167
177
  # Notice it is important to cache the ability object so it is not
168
178
  # recreated every time.
169
179
  def current_ability
170
180
  @current_ability ||= ::Ability.new(current_user)
171
181
  end
172
-
182
+
173
183
  # Use in the controller or view to check the user's permission for a given action
174
184
  # and object.
175
- #
185
+ #
176
186
  # can? :destroy, @project
177
- #
187
+ #
178
188
  # You can also pass the class instead of an instance (if you don't have one handy).
179
- #
189
+ #
180
190
  # <% if can? :create, Project %>
181
191
  # <%= link_to "New Project", new_project_path %>
182
192
  # <% end %>
183
- #
193
+ #
184
194
  # This simply calls "can?" on the current_ability. See Ability#can?.
185
195
  def can?(*args)
186
196
  current_ability.can?(*args)
187
197
  end
188
-
198
+
189
199
  # Convenience method which works the same as "can?" but returns the opposite value.
190
- #
200
+ #
191
201
  # cannot? :destroy, @project
192
- #
202
+ #
193
203
  def cannot?(*args)
194
204
  current_ability.cannot?(*args)
195
205
  end
@@ -1,4 +1,7 @@
1
1
  module CanCan
2
+ # Used internally to load and authorize a given controller resource.
3
+ # This manages finding or building an instance of the resource. If a
4
+ # parent is given it will go through the association.
2
5
  class ControllerResource # :nodoc:
3
6
  def initialize(controller, name, parent = nil, options = {})
4
7
  raise ImplementationRemoved, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
@@ -8,13 +11,17 @@ module CanCan
8
11
  @options = options
9
12
  end
10
13
 
14
+ # Returns the class used for this resource. This can be overriden by the :resource option.
15
+ # Sometimes one will use a symbol as the resource if a class does not exist for it. In that
16
+ # case "find" and "build" should not be called on it.
11
17
  def model_class
12
- if @options[:resource].nil?
18
+ resource_class = @options[:resource]
19
+ if resource_class.nil?
13
20
  @name.to_s.camelize.constantize
14
- elsif @options[:resource].kind_of? String
15
- @options[:resource].constantize
21
+ elsif resource_class.kind_of? String
22
+ resource_class.constantize
16
23
  else
17
- @options[:resource]
24
+ resource_class # could be a symbol
18
25
  end
19
26
  end
20
27
 
@@ -22,12 +29,10 @@ module CanCan
22
29
  self.model_instance ||= base.find(id)
23
30
  end
24
31
 
32
+ # Build a new instance of this resource. If it is a class we just call "new" otherwise
33
+ # it's an associaiton and "build" is used.
25
34
  def build(attributes)
26
- if base.kind_of? Class
27
- self.model_instance ||= base.new(attributes)
28
- else
29
- self.model_instance ||= base.build(attributes)
30
- end
35
+ self.model_instance ||= (base.kind_of?(Class) ? base.new(attributes) : base.build(attributes))
31
36
  end
32
37
 
33
38
  def model_instance
@@ -40,6 +45,8 @@ module CanCan
40
45
 
41
46
  private
42
47
 
48
+ # The object that methods (such as "find", "new" or "build") are called on.
49
+ # If there is a parent it will be the association, otherwise it will be the model's class.
43
50
  def base
44
51
  @parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class
45
52
  end
@@ -1,41 +1,41 @@
1
1
  module CanCan
2
2
  # A general CanCan exception
3
3
  class Error < StandardError; end
4
-
4
+
5
5
  # Raised when removed code is called, an alternative solution is provided in message.
6
6
  class ImplementationRemoved < Error; end
7
-
7
+
8
8
  # This error is raised when a user isn't allowed to access a given controller action.
9
9
  # This usually happens within a call to ControllerAdditions#authorize! but can be
10
10
  # raised manually.
11
- #
11
+ #
12
12
  # raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
13
- #
13
+ #
14
14
  # The passed message, action, and subject are optional and can later be retrieved when
15
15
  # rescuing from the exception.
16
- #
16
+ #
17
17
  # exception.message # => "Not authorized!"
18
18
  # exception.action # => :read
19
19
  # exception.subject # => Article
20
- #
21
- # If the message is not specified (or is nil) it will default to "You are anot authorized
20
+ #
21
+ # If the message is not specified (or is nil) it will default to "You are not authorized
22
22
  # to access this page." This default can be overridden by setting default_message.
23
- #
23
+ #
24
24
  # exception.default_message = "Default error message"
25
25
  # exception.message # => "Default error message"
26
- #
26
+ #
27
27
  # See ControllerAdditions#authorized! for more information on rescuing from this exception.
28
28
  class AccessDenied < Error
29
29
  attr_reader :action, :subject
30
30
  attr_writer :default_message
31
-
31
+
32
32
  def initialize(message = nil, action = nil, subject = nil)
33
33
  @message = message
34
34
  @action = action
35
35
  @subject = subject
36
36
  @default_message = "You are not authorized to access this page."
37
37
  end
38
-
38
+
39
39
  def to_s
40
40
  @message || @default_message
41
41
  end
@@ -1,44 +1,46 @@
1
1
  module CanCan
2
+ # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
3
+ # This class is used internally, so you do not need to call methods directly on it.
2
4
  class ResourceAuthorization # :nodoc:
3
- attr_reader :params
4
-
5
5
  def self.add_before_filter(controller_class, method, options = {})
6
6
  controller_class.before_filter(options.slice(:only, :except)) do |controller|
7
7
  ResourceAuthorization.new(controller, controller.params, options.except(:only, :except)).send(method)
8
8
  end
9
9
  end
10
-
10
+
11
11
  def initialize(controller, params, options = {})
12
12
  @controller = controller
13
13
  @params = params
14
14
  @options = options
15
15
  end
16
-
16
+
17
17
  def load_and_authorize_resource
18
18
  load_resource
19
19
  authorize_resource
20
20
  end
21
-
21
+
22
22
  def load_resource
23
- unless collection_actions.include? params[:action].to_sym
24
- if new_actions.include? params[:action].to_sym
25
- resource.build(params[model_name.to_sym])
26
- elsif params[:id]
27
- resource.find(params[:id])
23
+ if collection_actions.include? @params[:action].to_sym
24
+ parent_resource
25
+ else
26
+ if new_actions.include? @params[:action].to_sym
27
+ resource.build(@params[model_name.to_sym])
28
+ elsif @params[:id]
29
+ resource.find(@params[:id])
28
30
  end
29
31
  end
30
32
  end
31
-
33
+
32
34
  def authorize_resource
33
- @controller.authorize!(params[:action].to_sym, resource.model_instance || resource.model_class)
35
+ @controller.authorize!(@params[:action].to_sym, resource.model_instance || resource.model_class)
34
36
  end
35
-
37
+
36
38
  private
37
-
39
+
38
40
  def resource
39
41
  @resource ||= ControllerResource.new(@controller, model_name, parent_resource, @options)
40
42
  end
41
-
43
+
42
44
  def parent_resource
43
45
  parent = nil
44
46
  [@options[:nested]].flatten.compact.each do |name|
@@ -52,15 +54,15 @@ module CanCan
52
54
  end
53
55
  parent
54
56
  end
55
-
57
+
56
58
  def model_name
57
- params[:controller].sub("Controller", "").underscore.split('/').last.singularize
59
+ @options[:name] || @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
58
60
  end
59
-
61
+
60
62
  def collection_actions
61
63
  [:index] + [@options[:collection]].flatten
62
64
  end
63
-
65
+
64
66
  def new_actions
65
67
  [:new, :create] + [@options[:new]].flatten
66
68
  end
@@ -5,17 +5,17 @@ describe CanCan::Ability do
5
5
  @ability = Object.new
6
6
  @ability.extend(CanCan::Ability)
7
7
  end
8
-
8
+
9
9
  it "should be able to :read anything" do
10
10
  @ability.can :read, :all
11
11
  @ability.can?(:read, String).should be_true
12
12
  @ability.can?(:read, 123).should be_true
13
13
  end
14
-
14
+
15
15
  it "should not have permission to do something it doesn't know about" do
16
16
  @ability.can?(:foodfight, String).should be_false
17
17
  end
18
-
18
+
19
19
  it "should return what block returns on a can call" do
20
20
  @ability.can :read, :all
21
21
  @ability.can :read, Symbol do |sym|
@@ -24,21 +24,21 @@ describe CanCan::Ability do
24
24
  @ability.can?(:read, Symbol).should be_nil
25
25
  @ability.can?(:read, :some_symbol).should == :some_symbol
26
26
  end
27
-
27
+
28
28
  it "should pass class with object if :all objects are accepted" do
29
29
  @ability.can :preview, :all do |object_class, object|
30
30
  [object_class, object]
31
31
  end
32
32
  @ability.can?(:preview, 123).should == [Fixnum, 123]
33
33
  end
34
-
34
+
35
35
  it "should pass class with no object if :all objects are accepted and class is passed directly" do
36
36
  @ability.can :preview, :all do |object_class, object|
37
37
  [object_class, object]
38
38
  end
39
39
  @ability.can?(:preview, Hash).should == [Hash, nil]
40
40
  end
41
-
41
+
42
42
  it "should pass action and object for global manage actions" do
43
43
  @ability.can :manage, Array do |action, object|
44
44
  [action, object]
@@ -46,14 +46,14 @@ describe CanCan::Ability do
46
46
  @ability.can?(:stuff, [1, 2]).should == [:stuff, [1, 2]]
47
47
  @ability.can?(:stuff, Array).should == [:stuff, nil]
48
48
  end
49
-
49
+
50
50
  it "should alias update or destroy actions to modify action" do
51
51
  @ability.alias_action :update, :destroy, :to => :modify
52
52
  @ability.can(:modify, :all) { :modify_called }
53
53
  @ability.can?(:update, 123).should == :modify_called
54
54
  @ability.can?(:destroy, 123).should == :modify_called
55
55
  end
56
-
56
+
57
57
  it "should return block result for action, object_class, and object for any action" do
58
58
  @ability.can :manage, :all do |action, object_class, object|
59
59
  [action, object_class, object]
@@ -61,56 +61,56 @@ describe CanCan::Ability do
61
61
  @ability.can?(:foo, 123).should == [:foo, Fixnum, 123]
62
62
  @ability.can?(:bar, Fixnum).should == [:bar, Fixnum, nil]
63
63
  end
64
-
64
+
65
65
  it "should automatically alias index and show into read calls" do
66
66
  @ability.can :read, :all
67
67
  @ability.can?(:index, 123).should be_true
68
68
  @ability.can?(:show, 123).should be_true
69
69
  end
70
-
70
+
71
71
  it "should automatically alias new and edit into create and update respectively" do
72
72
  @ability.can(:create, :all) { :create_called }
73
73
  @ability.can(:update, :all) { :update_called }
74
74
  @ability.can?(:new, 123).should == :create_called
75
75
  @ability.can?(:edit, 123).should == :update_called
76
76
  end
77
-
77
+
78
78
  it "should not respond to prepare (now using initialize)" do
79
79
  @ability.should_not respond_to(:prepare)
80
80
  end
81
-
81
+
82
82
  it "should offer cannot? method which is simply invert of can?" do
83
83
  @ability.cannot?(:tie, String).should be_true
84
84
  end
85
-
85
+
86
86
  it "should be able to specify multiple actions and match any" do
87
87
  @ability.can [:read, :update], :all
88
88
  @ability.can?(:read, 123).should be_true
89
89
  @ability.can?(:update, 123).should be_true
90
90
  @ability.can?(:count, 123).should be_false
91
91
  end
92
-
92
+
93
93
  it "should be able to specify multiple classes and match any" do
94
94
  @ability.can :update, [String, Array]
95
95
  @ability.can?(:update, "foo").should be_true
96
96
  @ability.can?(:update, []).should be_true
97
97
  @ability.can?(:update, 123).should be_false
98
98
  end
99
-
99
+
100
100
  it "should support custom objects in the can definition" do
101
101
  @ability.can :read, :stats
102
102
  @ability.can?(:read, :stats).should be_true
103
103
  @ability.can?(:update, :stats).should be_false
104
104
  @ability.can?(:read, :nonstats).should be_false
105
105
  end
106
-
106
+
107
107
  it "should support 'cannot' method to define what user cannot do" do
108
108
  @ability.can :read, :all
109
109
  @ability.cannot :read, Integer
110
110
  @ability.can?(:read, "foo").should be_true
111
111
  @ability.can?(:read, 123).should be_false
112
112
  end
113
-
113
+
114
114
  it "should support block on 'cannot' method" do
115
115
  @ability.can :read, :all
116
116
  @ability.cannot :read, Integer do |int|
@@ -120,19 +120,19 @@ describe CanCan::Ability do
120
120
  @ability.can?(:read, 3).should be_true
121
121
  @ability.can?(:read, 123).should be_false
122
122
  end
123
-
123
+
124
124
  it "should append aliased actions" do
125
125
  @ability.alias_action :update, :to => :modify
126
126
  @ability.alias_action :destroy, :to => :modify
127
127
  @ability.aliased_actions[:modify].should == [:update, :destroy]
128
128
  end
129
-
129
+
130
130
  it "should clear aliased actions" do
131
131
  @ability.alias_action :update, :to => :modify
132
132
  @ability.clear_aliased_actions
133
133
  @ability.aliased_actions[:modify].should be_nil
134
134
  end
135
-
135
+
136
136
  it "should pass additional arguments to block from can?" do
137
137
  @ability.can :read, Integer do |int, x|
138
138
  int > x
@@ -140,52 +140,64 @@ describe CanCan::Ability do
140
140
  @ability.can?(:read, 2, 1).should be_true
141
141
  @ability.can?(:read, 2, 3).should be_false
142
142
  end
143
-
143
+
144
144
  it "should use conditions as third parameter and determine abilities from it" do
145
145
  @ability.can :read, Array, :first => 1, :last => 3
146
146
  @ability.can?(:read, [1, 2, 3]).should be_true
147
147
  @ability.can?(:read, [1, 2, 3, 4]).should be_false
148
148
  @ability.can?(:read, Array).should be_true
149
149
  end
150
-
150
+
151
151
  it "should allow an array of options in conditions hash" do
152
152
  @ability.can :read, Array, :first => [1, 3, 5]
153
153
  @ability.can?(:read, [1, 2, 3]).should be_true
154
154
  @ability.can?(:read, [2, 3]).should be_false
155
155
  @ability.can?(:read, [3, 4]).should be_true
156
156
  end
157
-
157
+
158
158
  it "should allow a range of options in conditions hash" do
159
159
  @ability.can :read, Array, :first => 1..3
160
160
  @ability.can?(:read, [1, 2, 3]).should be_true
161
161
  @ability.can?(:read, [3, 4]).should be_true
162
162
  @ability.can?(:read, [4, 5]).should be_false
163
163
  end
164
-
164
+
165
165
  it "should allow nested hashes in conditions hash" do
166
166
  @ability.can :read, Array, :first => { :length => 5 }
167
167
  @ability.can?(:read, ["foo", "bar"]).should be_false
168
168
  @ability.can?(:read, ["test1", "foo"]).should be_true
169
169
  end
170
-
170
+
171
+ it "should allow nested hash of arrays and match any element" do
172
+ @ability.can :read, Array, :first => { :to_i => 3 }
173
+ @ability.can?(:read, [[1, 2, 3]]).should be_true
174
+ @ability.can?(:read, [[4, 5, 6]]).should be_false
175
+ end
176
+
171
177
  it "should return conditions for a given ability" do
172
178
  @ability.can :read, Array, :first => 1, :last => 3
173
179
  @ability.conditions(:show, Array).should == {:first => 1, :last => 3}
174
180
  end
175
-
181
+
176
182
  it "should raise an exception when a block is used on condition" do
177
183
  @ability.can :read, Array do |a|
178
184
  true
179
185
  end
180
186
  lambda { @ability.conditions(:show, Array) }.should raise_error(CanCan::Error, "Cannot determine ability conditions from block for :show Array")
181
187
  end
182
-
188
+
183
189
  it "should return an empty hash for conditions when there are no conditions" do
184
190
  @ability.can :read, Array
185
191
  @ability.conditions(:show, Array).should == {}
186
192
  end
187
-
193
+
188
194
  it "should return false when performed on an action which isn't defined" do
189
195
  @ability.conditions(:foo, Array).should == false
190
196
  end
197
+
198
+ it "should has eated cheezburger" do
199
+ lambda {
200
+ @ability.can? :has, :cheezburger
201
+ }.should raise_exception(CanCan::Error, "Nom nom nom. I eated it.")
202
+ end
191
203
  end