cancan 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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