cancan 1.3.4 → 1.4.1
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.
- data/CHANGELOG.rdoc +52 -0
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.rdoc +31 -21
- data/Rakefile +5 -8
- data/lib/cancan/ability.rb +99 -32
- data/lib/cancan/active_record_additions.rb +1 -1
- data/lib/cancan/can_definition.rb +43 -14
- data/lib/cancan/controller_additions.rb +84 -12
- data/lib/cancan/controller_resource.rb +70 -12
- data/lib/cancan/exceptions.rb +5 -1
- data/lib/cancan/inherited_resource.rb +18 -0
- data/lib/cancan.rb +1 -0
- data/spec/cancan/ability_spec.rb +184 -59
- data/spec/cancan/active_record_additions_spec.rb +25 -1
- data/spec/cancan/can_definition_spec.rb +1 -0
- data/spec/cancan/controller_additions_spec.rb +34 -26
- data/spec/cancan/controller_resource_spec.rb +162 -87
- data/spec/cancan/inherited_resource_spec.rb +40 -0
- data/spec/cancan/query_spec.rb +48 -48
- data/spec/matchers.rb +1 -1
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -9
- metadata +79 -6
data/CHANGELOG.rdoc
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
1.4.1 (November 12, 2010)
|
|
2
|
+
|
|
3
|
+
* Renaming skip_authorization to skip_authorization_check - see issue #169
|
|
4
|
+
|
|
5
|
+
* Adding :through_association option to load_resource (thanks hunterae) - see issue #171
|
|
6
|
+
|
|
7
|
+
* The :shallow option now works with the :singleton option (thanks nandalopes) - see issue #187
|
|
8
|
+
|
|
9
|
+
* Play nicely with quick_scopes gem (thanks ramontayag) - see issue #183
|
|
10
|
+
|
|
11
|
+
* Fix odd behavior when "cache_classes = false" (thanks mphalliday) - see issue #174
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
1.4.0 (October 5, 2010)
|
|
15
|
+
|
|
16
|
+
* Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163
|
|
17
|
+
|
|
18
|
+
* Stop at 'cannot' definition when there are no conditions - see issue #161
|
|
19
|
+
|
|
20
|
+
* The :through option will now call a method with that name if instance variable doesn't exist - see issue #146
|
|
21
|
+
|
|
22
|
+
* Adding :shallow option to load_resource to bring back old behavior of fetching a child without a parent
|
|
23
|
+
|
|
24
|
+
* Raise AccessDenied error when loading a child and parent resource isn't found
|
|
25
|
+
|
|
26
|
+
* Abilities defined on a module will apply to anything that includes that module - see issue #150 and #152
|
|
27
|
+
|
|
28
|
+
* Abilities can be defined with a string of SQL in addition to a block so accessible_by works with a block - see issue #150
|
|
29
|
+
|
|
30
|
+
* Adding better support for InheritedResource - see issue #23
|
|
31
|
+
|
|
32
|
+
* Loading the collection instance variable (for index action) using accessible_by - see issue #137
|
|
33
|
+
|
|
34
|
+
* Adding action and subject variables to I18n unauthorized message - closes #142
|
|
35
|
+
|
|
36
|
+
* Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed (thanks justinko) - see issue #135
|
|
37
|
+
|
|
38
|
+
* Setting initial attributes based on ability conditions in new/create actions - see issue #114
|
|
39
|
+
|
|
40
|
+
* Check parent attributes for nested association in index action - see issue #121
|
|
41
|
+
|
|
42
|
+
* Supporting nesting in can? method using hash - see issue #121
|
|
43
|
+
|
|
44
|
+
* Adding I18n support for Access Denied messages (thanks EppO) - see issue #103
|
|
45
|
+
|
|
46
|
+
* Passing no arguments to +can+ definition will pass action, class, and object to block - see issue #129
|
|
47
|
+
|
|
48
|
+
* Don't pass action to block in +can+ definition when using :+manage+ option - see issue #129
|
|
49
|
+
|
|
50
|
+
* No longer calling block in +can+ definition when checking on class - see issue #116
|
|
51
|
+
|
|
52
|
+
|
|
1
53
|
1.3.4 (August 31, 2010)
|
|
2
54
|
|
|
3
55
|
* Don't stop at +cannot+ with hash conditions when checking class (thanks tamoya) - see issue #131
|
data/Gemfile
ADDED
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Wiki[http://wiki.github.com/ryanb/cancan] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
|
|
4
4
|
|
|
5
|
-
CanCan is an authorization
|
|
6
|
-
|
|
7
|
-
By default, the +current_user+ method is required, so if you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
|
|
5
|
+
CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the +Ability+ class) and not duplicated across controllers, views, and database queries.
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
== Installation
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
In <b>Rails 3</b>, add this to your Gemfile.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
gem "cancan"
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
In <b>Rails 2</b>, add this to your environment.rb file.
|
|
17
15
|
|
|
18
|
-
gem "cancan"
|
|
16
|
+
config.gem "cancan"
|
|
19
17
|
|
|
20
|
-
Alternatively
|
|
18
|
+
Alternatively, you can install it as a plugin.
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
rails plugin install git://github.com/ryanb/cancan.git
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
== Getting Started
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
CanCan expects a +current_user+ method to exist. If you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
|
|
26
|
+
|
|
27
|
+
Next create a class called +Ability+ in "models/ability.rb" or anywhere else in the load path. It should look similar to this.
|
|
28
28
|
|
|
29
29
|
class Ability
|
|
30
30
|
include CanCan::Ability
|
|
@@ -38,7 +38,7 @@ First, define a class called +Ability+ in "models/ability.rb" or anywhere else i
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
The +current_user+ is passed in to this method which is where the abilities are defined. See the "Defining Abilities" section below for more information.
|
|
42
42
|
|
|
43
43
|
The current user's permissions can be accessed using the "can?" and "cannot?" methods in the view and controller.
|
|
44
44
|
|
|
@@ -67,11 +67,11 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_
|
|
|
67
67
|
|
|
68
68
|
See {Authorizing Controller Actions}[http://wiki.github.com/ryanb/cancan/authorizing-controller-actions] for more information
|
|
69
69
|
|
|
70
|
-
If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
|
|
70
|
+
If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
|
|
71
71
|
|
|
72
72
|
class ApplicationController < ActionController::Base
|
|
73
73
|
rescue_from CanCan::AccessDenied do |exception|
|
|
74
|
-
flash[:
|
|
74
|
+
flash[:alert] = exception.message
|
|
75
75
|
redirect_to root_url
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -81,7 +81,7 @@ See {Exception Handling}[http://wiki.github.com/ryanb/cancan/exception-handling]
|
|
|
81
81
|
|
|
82
82
|
== Defining Abilities
|
|
83
83
|
|
|
84
|
-
As shown above, the +Ability+ class is where all user permissions are defined. The user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no
|
|
84
|
+
As shown above, the +Ability+ class is where all user permissions are defined. The current user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no assumption about how roles are handled in your application. See {Role Based Authorization}[http://wiki.github.com/ryanb/cancan/role-based-authorization] for an example.
|
|
85
85
|
|
|
86
86
|
The +can+ method is used to define permissions and requires 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.
|
|
87
87
|
|
|
@@ -97,7 +97,7 @@ Use :+manage+ to represent any action and :+all+ to represent any class. Here ar
|
|
|
97
97
|
can :read, :all # has permission to read any model
|
|
98
98
|
can :manage, :all # has permission to do anything to any model
|
|
99
99
|
|
|
100
|
-
You can pass a hash of conditions as the third argument to further
|
|
100
|
+
You can pass a hash of conditions as the third argument to further define what the user is able to access. Here the user will only have permission to read active projects which he owns.
|
|
101
101
|
|
|
102
102
|
can :read, Project, :active => true, :user_id => user.id
|
|
103
103
|
|
|
@@ -106,10 +106,10 @@ See {Defining Abilities with Hashes}[http://wiki.github.com/ryanb/cancan/definin
|
|
|
106
106
|
Blocks can also be used if you need more control.
|
|
107
107
|
|
|
108
108
|
can :update, Project do |project|
|
|
109
|
-
project
|
|
109
|
+
project.groups.include?(user.group)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
If the block returns true then the user has that
|
|
112
|
+
If the block returns true then the user has that ability for that project, otherwise he will be denied access. See {Defining Abilities with Blocks}[http://wiki.github.com/ryanb/cancan/defining-abilities-with-blocks] for more information.
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
== Aliasing Actions
|
|
@@ -120,33 +120,43 @@ You will usually be working with four actions when defining and checking permiss
|
|
|
120
120
|
alias_action :new, :to => :create
|
|
121
121
|
alias_action :edit, :to => :update
|
|
122
122
|
|
|
123
|
-
Notice the +edit+ action is aliased to +update+.
|
|
123
|
+
Notice the +edit+ action is aliased to +update+. This means if the user is able to update a record he also has permission to edit it. You can define your own aliases in the +Ability+ class.
|
|
124
124
|
|
|
125
125
|
alias_action :update, :destroy, :to => :modify
|
|
126
126
|
can :modify, Comment
|
|
127
127
|
can? :update, Comment # => true
|
|
128
128
|
|
|
129
|
-
See {Custom Actions}[http://wiki.github.com/ryanb/cancan/custom-actions] for information on adding other actions.
|
|
129
|
+
The +alias_action+ method is an instance method and usually called in +initialize+. See {Custom Actions}[http://wiki.github.com/ryanb/cancan/custom-actions] for information on adding other actions.
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
== Fetching Records
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
It is possible to fetch records which the user has permission to read using the +accessible_by+ scope in Active Record.
|
|
135
135
|
|
|
136
136
|
@articles = Article.accessible_by(current_ability)
|
|
137
137
|
|
|
138
|
+
Since version 1.4 this is done automatically when loading resources in the index action, so one rarely needs to do it manually.
|
|
139
|
+
|
|
138
140
|
This will only work when abilities are defined using hash conditions, not blocks. See {Fetching Records}[http://wiki.github.com/ryanb/cancan/fetching-records] for more information.
|
|
139
141
|
|
|
140
142
|
|
|
141
143
|
== Additional Docs
|
|
142
144
|
|
|
143
|
-
* {Upgrading to 1.
|
|
145
|
+
* {Upgrading to 1.4}[http://github.com/ryanb/cancan/wiki/Upgrading-to-1.4]
|
|
144
146
|
* {Nested Resources}[http://wiki.github.com/ryanb/cancan/nested-resources]
|
|
145
147
|
* {Testing Abilities}[http://wiki.github.com/ryanb/cancan/testing-abilities]
|
|
146
148
|
* {Accessing Request Data}[http://wiki.github.com/ryanb/cancan/accessing-request-data]
|
|
147
149
|
* {Admin Namespace}[http://wiki.github.com/ryanb/cancan/admin-namespace]
|
|
148
150
|
* {See more}[http://wiki.github.com/ryanb/cancan/]
|
|
149
151
|
|
|
152
|
+
|
|
153
|
+
== Questions or Problems?
|
|
154
|
+
|
|
155
|
+
If you have any issues with CanCan which you cannot find the solution to in the documentation, please add an {issue on GitHub}[http://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
|
|
156
|
+
|
|
157
|
+
To get the specs running you should call +bundle+ and then +rake+. Specs currently do not work in Ruby 1.9 due to the RR mocking framework.
|
|
158
|
+
|
|
159
|
+
|
|
150
160
|
== Special Thanks
|
|
151
161
|
|
|
152
162
|
CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[http://github.com/ryanb/cancan/contributors]. See the CHANGELOG[http://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.
|
data/Rakefile
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'rake'
|
|
3
|
-
require '
|
|
3
|
+
require 'rspec/core/rake_task'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Spec::Rake::SpecTask.new do |t|
|
|
9
|
-
t.spec_files = spec_files
|
|
10
|
-
t.spec_opts = ["-c"]
|
|
5
|
+
desc "Run RSpec"
|
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
|
7
|
+
t.verbose = false
|
|
11
8
|
end
|
|
12
9
|
|
|
13
|
-
task :default => :spec
|
|
10
|
+
task :default => :spec
|
data/lib/cancan/ability.rb
CHANGED
|
@@ -16,7 +16,7 @@ module CanCan
|
|
|
16
16
|
# end
|
|
17
17
|
#
|
|
18
18
|
module Ability
|
|
19
|
-
#
|
|
19
|
+
# Check if the user has permission to perform a given action on an object.
|
|
20
20
|
#
|
|
21
21
|
# can? :destroy, @project
|
|
22
22
|
#
|
|
@@ -24,6 +24,11 @@ module CanCan
|
|
|
24
24
|
#
|
|
25
25
|
# can? :create, Project
|
|
26
26
|
#
|
|
27
|
+
# Nested resources can be passed through a hash, this way conditions which are
|
|
28
|
+
# dependent upon the association will work when using a class.
|
|
29
|
+
#
|
|
30
|
+
# can? :create, @category => Project
|
|
31
|
+
#
|
|
27
32
|
# Any additional arguments will be passed into the "can" block definition. This
|
|
28
33
|
# can be used to pass more information about the user's request for example.
|
|
29
34
|
#
|
|
@@ -49,8 +54,7 @@ module CanCan
|
|
|
49
54
|
#
|
|
50
55
|
# Also see the RSpec Matchers to aid in testing.
|
|
51
56
|
def can?(action, subject, *extra_args)
|
|
52
|
-
|
|
53
|
-
match = relevant_can_definitions(action, subject).detect do |can_definition|
|
|
57
|
+
match = relevant_can_definitions_for_match(action, subject).detect do |can_definition|
|
|
54
58
|
can_definition.matches_conditions?(action, subject, extra_args)
|
|
55
59
|
end
|
|
56
60
|
match ? match.base_behavior : false
|
|
@@ -70,54 +74,54 @@ module CanCan
|
|
|
70
74
|
# can :update, Article
|
|
71
75
|
#
|
|
72
76
|
# You can pass an array for either of these parameters to match any one.
|
|
77
|
+
# Here the user has the ability to update or destroy both articles and comments.
|
|
73
78
|
#
|
|
74
79
|
# can [:update, :destroy], [Article, Comment]
|
|
75
80
|
#
|
|
76
|
-
#
|
|
81
|
+
# You can pass :all to match any object and :manage to match any action. Here are some examples.
|
|
82
|
+
#
|
|
83
|
+
# can :manage, :all
|
|
84
|
+
# can :update, :all
|
|
85
|
+
# can :manage, Project
|
|
77
86
|
#
|
|
78
|
-
# You can pass a hash of conditions as the third argument.
|
|
87
|
+
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
|
79
88
|
#
|
|
80
89
|
# can :read, Project, :active => true, :user_id => user.id
|
|
81
90
|
#
|
|
82
|
-
#
|
|
83
|
-
# for
|
|
91
|
+
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
|
|
92
|
+
# are also used for initial attributes when building a record in ControllerAdditions#load_resource.
|
|
84
93
|
#
|
|
85
|
-
# If the conditions hash does not give you enough control over defining abilities, you can use a block
|
|
86
|
-
#
|
|
94
|
+
# If the conditions hash does not give you enough control over defining abilities, you can use a block
|
|
95
|
+
# along with any Ruby code you want.
|
|
87
96
|
#
|
|
88
97
|
# can :update, Project do |project|
|
|
89
|
-
# project
|
|
98
|
+
# project.groups.include?(user.group)
|
|
90
99
|
# end
|
|
91
100
|
#
|
|
92
101
|
# If the block returns true then the user has that :update ability for that project, otherwise he
|
|
93
|
-
# will be denied access.
|
|
94
|
-
#
|
|
102
|
+
# will be denied access. The downside to using a block is that it cannot be used to generate
|
|
103
|
+
# conditions for database queries.
|
|
95
104
|
#
|
|
96
|
-
#
|
|
105
|
+
# You can pass custom objects into this "can" method, this is usually done with a symbol
|
|
106
|
+
# and is useful if a class isn't available to define permissions on.
|
|
97
107
|
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
108
|
+
# can :read, :stats
|
|
109
|
+
# can? :read, :stats # => true
|
|
100
110
|
#
|
|
101
|
-
#
|
|
102
|
-
# object_class != Order
|
|
103
|
-
# end
|
|
111
|
+
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
|
|
104
112
|
#
|
|
105
|
-
#
|
|
113
|
+
# can :update, Project, :priority => 3
|
|
114
|
+
# can? :update, Project # => true
|
|
106
115
|
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
116
|
+
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
|
|
117
|
+
# block will always be executed. This allows you to override the full behavior if the permissions are
|
|
118
|
+
# defined in an external source such as the database.
|
|
109
119
|
#
|
|
110
|
-
# can
|
|
111
|
-
#
|
|
120
|
+
# can do |action, object_class, object|
|
|
121
|
+
# # check the database and return true/false
|
|
112
122
|
# end
|
|
113
123
|
#
|
|
114
|
-
|
|
115
|
-
# and is useful if a class isn't available to define permissions on.
|
|
116
|
-
#
|
|
117
|
-
# can :read, :stats
|
|
118
|
-
# can? :read, :stats # => true
|
|
119
|
-
#
|
|
120
|
-
def can(action, subject, conditions = nil, &block)
|
|
124
|
+
def can(action = nil, subject = nil, conditions = nil, &block)
|
|
121
125
|
can_definitions << CanDefinition.new(true, action, subject, conditions, block)
|
|
122
126
|
end
|
|
123
127
|
|
|
@@ -133,7 +137,7 @@ module CanCan
|
|
|
133
137
|
# product.invisible?
|
|
134
138
|
# end
|
|
135
139
|
#
|
|
136
|
-
def cannot(action, subject, conditions = nil, &block)
|
|
140
|
+
def cannot(action = nil, subject = nil, conditions = nil, &block)
|
|
137
141
|
can_definitions << CanDefinition.new(false, action, subject, conditions, block)
|
|
138
142
|
end
|
|
139
143
|
|
|
@@ -189,9 +193,54 @@ module CanCan
|
|
|
189
193
|
Query.new(subject, relevant_can_definitions_for_query(action, subject))
|
|
190
194
|
end
|
|
191
195
|
|
|
196
|
+
# See ControllerAdditions#authorize! for documentation.
|
|
197
|
+
def authorize!(action, subject, *args)
|
|
198
|
+
message = nil
|
|
199
|
+
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
|
|
200
|
+
message = args.pop[:message]
|
|
201
|
+
end
|
|
202
|
+
if cannot?(action, subject, *args)
|
|
203
|
+
message ||= unauthorized_message(action, subject)
|
|
204
|
+
raise AccessDenied.new(message, action, subject)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def unauthorized_message(action, subject)
|
|
209
|
+
keys = unauthorized_message_keys(action, subject)
|
|
210
|
+
variables = {:action => action.to_s}
|
|
211
|
+
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
|
|
212
|
+
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
|
213
|
+
message.blank? ? nil : message
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def attributes_for(action, subject)
|
|
217
|
+
attributes = {}
|
|
218
|
+
relevant_can_definitions(action, subject).map do |can_definition|
|
|
219
|
+
attributes.merge!(can_definition.attributes_from_conditions) if can_definition.base_behavior
|
|
220
|
+
end
|
|
221
|
+
attributes
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def has_block?(action, subject)
|
|
225
|
+
relevant_can_definitions(action, subject).any?(&:only_block?)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def has_raw_sql?(action, subject)
|
|
229
|
+
relevant_can_definitions(action, subject).any?(&:only_raw_sql?)
|
|
230
|
+
end
|
|
231
|
+
|
|
192
232
|
private
|
|
193
233
|
|
|
194
|
-
|
|
234
|
+
def unauthorized_message_keys(action, subject)
|
|
235
|
+
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
|
|
236
|
+
[subject, :all].map do |try_subject|
|
|
237
|
+
[aliases_for_action(action), :manage].flatten.map do |try_action|
|
|
238
|
+
:"#{try_action}.#{try_subject}"
|
|
239
|
+
end
|
|
240
|
+
end.flatten
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Accepts an array of actions and returns an array of actions which match.
|
|
195
244
|
# This should be called before "matches?" and other checking methods since they
|
|
196
245
|
# rely on the actions to be expanded.
|
|
197
246
|
def expand_actions(actions)
|
|
@@ -200,6 +249,16 @@ module CanCan
|
|
|
200
249
|
end.flatten
|
|
201
250
|
end
|
|
202
251
|
|
|
252
|
+
# Given an action, it will try to find all of the actions which are aliased to it.
|
|
253
|
+
# This does the opposite kind of lookup as expand_actions.
|
|
254
|
+
def aliases_for_action(action)
|
|
255
|
+
results = [action]
|
|
256
|
+
aliased_actions.each do |aliased_action, actions|
|
|
257
|
+
results += aliases_for_action(aliased_action) if actions.include? action
|
|
258
|
+
end
|
|
259
|
+
results
|
|
260
|
+
end
|
|
261
|
+
|
|
203
262
|
def can_definitions
|
|
204
263
|
@can_definitions ||= []
|
|
205
264
|
end
|
|
@@ -213,6 +272,14 @@ module CanCan
|
|
|
213
272
|
end
|
|
214
273
|
end
|
|
215
274
|
|
|
275
|
+
def relevant_can_definitions_for_match(action, subject)
|
|
276
|
+
relevant_can_definitions(action, subject).each do |can_definition|
|
|
277
|
+
if can_definition.only_raw_sql?
|
|
278
|
+
raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
216
283
|
def relevant_can_definitions_for_query(action, subject)
|
|
217
284
|
relevant_can_definitions(action, subject).each do |can_definition|
|
|
218
285
|
if can_definition.only_block?
|
|
@@ -21,7 +21,7 @@ module CanCan
|
|
|
21
21
|
# internally uses Ability#conditions method, see that for more information.
|
|
22
22
|
def accessible_by(ability, action = :read)
|
|
23
23
|
query = ability.query(action, self)
|
|
24
|
-
if respond_to? :
|
|
24
|
+
if respond_to?(:where) && respond_to?(:joins)
|
|
25
25
|
where(query.conditions).joins(query.joins)
|
|
26
26
|
else
|
|
27
27
|
scoped(:conditions => query.conditions, :joins => query.joins)
|
|
@@ -11,6 +11,7 @@ module CanCan
|
|
|
11
11
|
# and subject respectively (such as :read, @project). The third argument is a hash
|
|
12
12
|
# of conditions and the last one is the block passed to the "can" call.
|
|
13
13
|
def initialize(base_behavior, action, subject, conditions, block)
|
|
14
|
+
@match_all = action.nil? && subject.nil?
|
|
14
15
|
@base_behavior = base_behavior
|
|
15
16
|
@actions = [action].flatten
|
|
16
17
|
@subjects = [subject].flatten
|
|
@@ -20,21 +21,28 @@ module CanCan
|
|
|
20
21
|
|
|
21
22
|
# Matches both the subject and action, not necessarily the conditions
|
|
22
23
|
def relevant?(action, subject)
|
|
23
|
-
|
|
24
|
+
subject = subject.values.first if subject.kind_of? Hash
|
|
25
|
+
@match_all || (matches_action?(action) && matches_subject?(subject))
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Matches the block or conditions hash
|
|
27
29
|
def matches_conditions?(action, subject, extra_args)
|
|
28
|
-
if @
|
|
29
|
-
|
|
30
|
-
elsif @
|
|
30
|
+
if @match_all
|
|
31
|
+
call_block_with_all(action, subject, extra_args)
|
|
32
|
+
elsif @block && !subject_class?(subject)
|
|
33
|
+
@block.call(subject, *extra_args)
|
|
34
|
+
elsif @conditions.kind_of?(Hash) && subject.kind_of?(Hash)
|
|
35
|
+
nested_subject_matches_conditions?(subject)
|
|
36
|
+
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
|
31
37
|
matches_conditions_hash?(subject)
|
|
32
38
|
else
|
|
33
|
-
|
|
39
|
+
# Don't stop at "cannot" definitions when there are conditions.
|
|
40
|
+
@conditions.empty? ? true : @base_behavior
|
|
34
41
|
end
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
def tableized_conditions(conditions = @conditions)
|
|
45
|
+
return conditions unless conditions.kind_of? Hash
|
|
38
46
|
conditions.inject({}) do |result_hash, (name, value)|
|
|
39
47
|
if value.kind_of? Hash
|
|
40
48
|
name = name.to_s.tableize.to_sym
|
|
@@ -49,6 +57,10 @@ module CanCan
|
|
|
49
57
|
conditions_empty? && !@block.nil?
|
|
50
58
|
end
|
|
51
59
|
|
|
60
|
+
def only_raw_sql?
|
|
61
|
+
@block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
|
|
62
|
+
end
|
|
63
|
+
|
|
52
64
|
def conditions_empty?
|
|
53
65
|
@conditions == {} || @conditions.nil?
|
|
54
66
|
end
|
|
@@ -57,12 +69,25 @@ module CanCan
|
|
|
57
69
|
hash = {}
|
|
58
70
|
conditions.map do |name, value|
|
|
59
71
|
hash[name] = associations_hash(value) if value.kind_of? Hash
|
|
60
|
-
end
|
|
72
|
+
end if conditions.kind_of? Hash
|
|
61
73
|
hash
|
|
62
74
|
end
|
|
63
75
|
|
|
76
|
+
def attributes_from_conditions
|
|
77
|
+
attributes = {}
|
|
78
|
+
@conditions.each do |key, value|
|
|
79
|
+
attributes[key] = value unless [Array, Range, Hash].include? value.class
|
|
80
|
+
end if @conditions.kind_of? Hash
|
|
81
|
+
attributes
|
|
82
|
+
end
|
|
83
|
+
|
|
64
84
|
private
|
|
65
85
|
|
|
86
|
+
def subject_class?(subject)
|
|
87
|
+
klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
|
88
|
+
klass == Class || klass == Module
|
|
89
|
+
end
|
|
90
|
+
|
|
66
91
|
def matches_action?(action)
|
|
67
92
|
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
|
|
68
93
|
end
|
|
@@ -72,7 +97,7 @@ module CanCan
|
|
|
72
97
|
end
|
|
73
98
|
|
|
74
99
|
def matches_subject_class?(subject)
|
|
75
|
-
@subjects.any? { |sub| sub.kind_of?(
|
|
100
|
+
@subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
|
|
76
101
|
end
|
|
77
102
|
|
|
78
103
|
def matches_conditions_hash?(subject, conditions = @conditions)
|
|
@@ -92,13 +117,17 @@ module CanCan
|
|
|
92
117
|
end
|
|
93
118
|
end
|
|
94
119
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
def nested_subject_matches_conditions?(subject_hash)
|
|
121
|
+
parent, child = subject_hash.shift
|
|
122
|
+
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def call_block_with_all(action, subject, extra_args)
|
|
126
|
+
if subject.class == Class
|
|
127
|
+
@block.call(action, subject, nil, *extra_args)
|
|
128
|
+
else
|
|
129
|
+
@block.call(action, subject.class, subject, *extra_args)
|
|
130
|
+
end
|
|
102
131
|
end
|
|
103
132
|
end
|
|
104
133
|
end
|