cancancan 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.rdoc +427 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +23 -0
- data/LICENSE +20 -0
- data/README.rdoc +161 -0
- data/Rakefile +18 -0
- data/init.rb +1 -0
- data/lib/cancan.rb +13 -0
- data/lib/cancan/ability.rb +324 -0
- data/lib/cancan/controller_additions.rb +397 -0
- data/lib/cancan/controller_resource.rb +286 -0
- data/lib/cancan/exceptions.rb +50 -0
- data/lib/cancan/inherited_resource.rb +20 -0
- data/lib/cancan/matchers.rb +14 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
- data/lib/cancan/model_adapters/default_adapter.rb +7 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
- data/lib/cancan/model_additions.rb +31 -0
- data/lib/cancan/rule.rb +147 -0
- data/lib/cancancan.rb +1 -0
- data/lib/generators/cancan/ability/USAGE +4 -0
- data/lib/generators/cancan/ability/ability_generator.rb +11 -0
- data/lib/generators/cancan/ability/templates/ability.rb +32 -0
- data/spec/README.rdoc +28 -0
- data/spec/cancan/ability_spec.rb +455 -0
- data/spec/cancan/controller_additions_spec.rb +141 -0
- data/spec/cancan/controller_resource_spec.rb +553 -0
- data/spec/cancan/exceptions_spec.rb +58 -0
- data/spec/cancan/inherited_resource_spec.rb +60 -0
- data/spec/cancan/matchers_spec.rb +29 -0
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
- data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
- data/spec/cancan/rule_spec.rb +52 -0
- data/spec/matchers.rb +13 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +77 -0
- metadata +126 -0
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
case ENV["MODEL_ADAPTER"]
|
4
|
+
when nil, "active_record"
|
5
|
+
# Sqlite for CRuby, Rubinius, including Windows and RubyInstaller
|
6
|
+
gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
|
7
|
+
# Sqlite for JRuby
|
8
|
+
gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
|
9
|
+
gem "activerecord", '~> 3.0.9', :require => "active_record"
|
10
|
+
gem "with_model", "~> 0.2.5"
|
11
|
+
gem "meta_where"
|
12
|
+
when "data_mapper"
|
13
|
+
gem "dm-core", "~> 1.0.2"
|
14
|
+
gem "dm-sqlite-adapter", "~> 1.0.2"
|
15
|
+
gem "dm-migrations", "~> 1.0.2"
|
16
|
+
when "mongoid"
|
17
|
+
gem "bson_ext", "~> 1.1"
|
18
|
+
gem "mongoid", "~> 2.0.0.beta.20"
|
19
|
+
else
|
20
|
+
raise "Unknown model adapter: #{ENV["MODEL_ADAPTER"]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
gemspec
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ryan Bates
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
= CanCan
|
2
|
+
{<img src="https://fury-badge.herokuapp.com/rb/cancancan.png" alt="Gem Version" />}[http://badge.fury.io/rb/cancancan]
|
3
|
+
{<img src="https://secure.travis-ci.org/bryanrite/cancancan.png?branch=master" />}[http://travis-ci.org/bryanrite/cancancan]
|
4
|
+
{<img src="https://codeclimate.com/github/bryanrite/cancancan.png" />}[https://codeclimate.com/github/bryanrite/cancancan]
|
5
|
+
|
6
|
+
Wiki[https://github.com/bryanrite/cancancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
|
7
|
+
|
8
|
+
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.
|
9
|
+
|
10
|
+
|
11
|
+
== Mission
|
12
|
+
|
13
|
+
This repo is a continuation of the dead CanCan[https://github.com/rbates/cancan] project. Our mission is to keep CanCan alive and moving forward, with maintenance fixes and new features. Pull Requests are welcome!
|
14
|
+
|
15
|
+
I am currently focusing on the 1.x branch for the immediate future, making sure it is up to date as well as ensuring compatibility with Rails 4+. I will take a look into the 2.x branch and try to see what improvements, reorganizations and redesigns Ryan was attempting and go forward from there.
|
16
|
+
|
17
|
+
Any help is greatly appreciated, feel free to submit pull-requests or open issues.
|
18
|
+
|
19
|
+
|
20
|
+
== Installation
|
21
|
+
|
22
|
+
In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
|
23
|
+
|
24
|
+
gem 'cancancan', '~> 1.7'
|
25
|
+
|
26
|
+
In <b>Rails 2</b>, add this to your environment.rb file.
|
27
|
+
|
28
|
+
config.gem "cancancan"
|
29
|
+
|
30
|
+
Alternatively, you can install it as a plugin.
|
31
|
+
|
32
|
+
rails plugin install git://github.com/bryanrite/cancancan.git
|
33
|
+
|
34
|
+
|
35
|
+
== Getting Started
|
36
|
+
|
37
|
+
CanCan expects a +current_user+ method to exist in the controller. First, set up some authentication (such as Authlogic[https://github.com/binarylogic/authlogic] or Devise[https://github.com/plataformatec/devise]). See {Changing Defaults}[https://github.com/bryanrite/cancan/wiki/changing-defaults] if you need different behavior.
|
38
|
+
|
39
|
+
|
40
|
+
=== 1. Define Abilities
|
41
|
+
|
42
|
+
User permissions are defined in an +Ability+ class. CanCan 1.5 includes a Rails 3 generator for creating this class.
|
43
|
+
|
44
|
+
rails g cancan:ability
|
45
|
+
|
46
|
+
In Rails 2.3, just add a new class in <tt>app/models/ability.rb</tt> with the following contents:
|
47
|
+
|
48
|
+
class Ability
|
49
|
+
include CanCan::Ability
|
50
|
+
|
51
|
+
def initialize(user)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
See {Defining Abilities}[https://github.com/bryanrite/cancan/wiki/defining-abilities] for details.
|
56
|
+
|
57
|
+
|
58
|
+
=== 2. Check Abilities & Authorization
|
59
|
+
|
60
|
+
The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
|
61
|
+
|
62
|
+
<% if can? :update, @article %>
|
63
|
+
<%= link_to "Edit", edit_article_path(@article) %>
|
64
|
+
<% end %>
|
65
|
+
|
66
|
+
See {Checking Abilities}[https://github.com/bryanrite/cancancan/wiki/checking-abilities] for more information
|
67
|
+
|
68
|
+
The <tt>authorize!</tt> method in the controller will raise an exception if the user is not able to perform the given action.
|
69
|
+
|
70
|
+
def show
|
71
|
+
@article = Article.find(params[:id])
|
72
|
+
authorize! :read, @article
|
73
|
+
end
|
74
|
+
|
75
|
+
Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.
|
76
|
+
|
77
|
+
class ArticlesController < ApplicationController
|
78
|
+
load_and_authorize_resource
|
79
|
+
|
80
|
+
def show
|
81
|
+
# @article is already loaded and authorized
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
See {Authorizing Controller Actions}[https://github.com/bryanrite/cancancan/wiki/authorizing-controller-actions] for more information.
|
86
|
+
|
87
|
+
|
88
|
+
==== Strong Parameters
|
89
|
+
|
90
|
+
When using <tt>strong_parameters</tt> or Rails 4+, you have to sanitize inputs before saving the record, in actions such as <tt>:create</tt> and <tt>:update</tt>.
|
91
|
+
|
92
|
+
By default, CanCan will try to sanitize the input on <tt>:create</tt> and <tt>:update</tt> routes by seeing if your controller will respond to the following methods (in order):
|
93
|
+
|
94
|
+
* <tt>create_params</tt> or <tt>update_params</tt> (depending on the action you are performing)
|
95
|
+
* <tt><model_name>_params</tt> such as <tt>article_params</tt> (this is the default convention in rails for naming your param method)
|
96
|
+
* <tt>resource_params</tt> (a generically named method you could specify in each controller)
|
97
|
+
|
98
|
+
Additionally, <tt>load_and_authorize_resource</tt> can now take a <tt>param_method</tt> option to specify a custom method in the controller to run to sanitize input.
|
99
|
+
|
100
|
+
class ArticlesController < ApplicationController
|
101
|
+
load_and_authorize_resource param_method: :my_sanitizer
|
102
|
+
|
103
|
+
def create
|
104
|
+
if @article.save
|
105
|
+
# hurray
|
106
|
+
else
|
107
|
+
render :new
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def my_sanitizer
|
114
|
+
params.require(:article).permit(:name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
=== 3. Handle Unauthorized Access
|
119
|
+
|
120
|
+
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+.
|
121
|
+
|
122
|
+
class ApplicationController < ActionController::Base
|
123
|
+
rescue_from CanCan::AccessDenied do |exception|
|
124
|
+
redirect_to root_url, :alert => exception.message
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
See {Exception Handling}[https://github.com/bryanrite/cancancan/wiki/exception-handling] for more information.
|
129
|
+
|
130
|
+
|
131
|
+
=== 4. Lock It Down
|
132
|
+
|
133
|
+
If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
|
134
|
+
|
135
|
+
class ApplicationController < ActionController::Base
|
136
|
+
check_authorization
|
137
|
+
end
|
138
|
+
|
139
|
+
This will raise an exception if authorization is not performed in an action. If you want to skip this add +skip_authorization_check+ to a controller subclass. See {Ensure Authorization}[https://github.com/bryanrite/cancancan/wiki/Ensure-Authorization] for more information.
|
140
|
+
|
141
|
+
|
142
|
+
== Wiki Docs
|
143
|
+
|
144
|
+
* {Upgrading to 1.6}[https://github.com/bryanrite/cancancan/wiki/Upgrading-to-1.6]
|
145
|
+
* {Defining Abilities}[https://github.com/bryanrite/cancancan/wiki/Defining-Abilities]
|
146
|
+
* {Checking Abilities}[https://github.com/bryanrite/cancancan/wiki/Checking-Abilities]
|
147
|
+
* {Authorizing Controller Actions}[https://github.com/bryanrite/cancancan/wiki/Authorizing-Controller-Actions]
|
148
|
+
* {Exception Handling}[https://github.com/bryanrite/cancancan/wiki/Exception-Handling]
|
149
|
+
* {Changing Defaults}[https://github.com/bryanrite/cancancan/wiki/Changing-Defaults]
|
150
|
+
* {See more}[https://github.com/bryanrite/cancancan/wiki]
|
151
|
+
|
152
|
+
== Questions or Problems?
|
153
|
+
|
154
|
+
If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/bryanrite/cancancan/wiki], please add an {issue on GitHub}[https://github.com/bryanrite/cancancan/issues] or fork the project and send a pull request.
|
155
|
+
|
156
|
+
To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/bryanrite/cancancan/blob/master/spec/README.rdoc] for more information.
|
157
|
+
|
158
|
+
|
159
|
+
== Special Thanks
|
160
|
+
|
161
|
+
CanCan was inspired by declarative_authorization[https://github.com/stffn/declarative_authorization/] and aegis[https://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[https://github.com/bryanrite/cancancan/contributors]. See the CHANGELOG[https://github.com/bryanrite/cancancan/blob/master/CHANGELOG.rdoc] for the full list.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc "Run RSpec"
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.verbose = false
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Run specs for all adapters"
|
11
|
+
task :spec_all do
|
12
|
+
%w[active_record data_mapper mongoid].each do |model_adapter|
|
13
|
+
puts "MODEL_ADAPTER = #{model_adapter}"
|
14
|
+
system "rake spec MODEL_ADAPTER=#{model_adapter}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :spec
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'cancan'
|
data/lib/cancan.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'cancan/ability'
|
2
|
+
require 'cancan/rule'
|
3
|
+
require 'cancan/controller_resource'
|
4
|
+
require 'cancan/controller_additions'
|
5
|
+
require 'cancan/model_additions'
|
6
|
+
require 'cancan/exceptions'
|
7
|
+
require 'cancan/inherited_resource'
|
8
|
+
|
9
|
+
require 'cancan/model_adapters/abstract_adapter'
|
10
|
+
require 'cancan/model_adapters/default_adapter'
|
11
|
+
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
|
12
|
+
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
|
13
|
+
require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
|
@@ -0,0 +1,324 @@
|
|
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
|
+
# Check if the user has permission to perform a given action on an object.
|
20
|
+
#
|
21
|
+
# can? :destroy, @project
|
22
|
+
#
|
23
|
+
# You can also pass the class instead of an instance (if you don't have one handy).
|
24
|
+
#
|
25
|
+
# can? :create, Project
|
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
|
+
#
|
32
|
+
# Any additional arguments will be passed into the "can" block definition. This
|
33
|
+
# can be used to pass more information about the user's request for example.
|
34
|
+
#
|
35
|
+
# can? :create, Project, request.remote_ip
|
36
|
+
#
|
37
|
+
# can :create, Project do |project, remote_ip|
|
38
|
+
# # ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Not only can you use the can? method in the controller and view (see ControllerAdditions),
|
42
|
+
# but you can also call it directly on an ability instance.
|
43
|
+
#
|
44
|
+
# ability.can? :destroy, @project
|
45
|
+
#
|
46
|
+
# This makes testing a user's abilities very easy.
|
47
|
+
#
|
48
|
+
# def test "user can only destroy projects which he owns"
|
49
|
+
# user = User.new
|
50
|
+
# ability = Ability.new(user)
|
51
|
+
# assert ability.can?(:destroy, Project.new(:user => user))
|
52
|
+
# assert ability.cannot?(:destroy, Project.new)
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# Also see the RSpec Matchers to aid in testing.
|
56
|
+
def can?(action, subject, *extra_args)
|
57
|
+
match = relevant_rules_for_match(action, subject).detect do |rule|
|
58
|
+
rule.matches_conditions?(action, subject, extra_args)
|
59
|
+
end
|
60
|
+
match ? match.base_behavior : false
|
61
|
+
end
|
62
|
+
|
63
|
+
# Convenience method which works the same as "can?" but returns the opposite value.
|
64
|
+
#
|
65
|
+
# cannot? :destroy, @project
|
66
|
+
#
|
67
|
+
def cannot?(*args)
|
68
|
+
!can?(*args)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Defines which abilities are allowed using two arguments. The first one is the action
|
72
|
+
# you're setting the permission for, the second one is the class of object you're setting it on.
|
73
|
+
#
|
74
|
+
# can :update, Article
|
75
|
+
#
|
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.
|
78
|
+
#
|
79
|
+
# can [:update, :destroy], [Article, Comment]
|
80
|
+
#
|
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
|
86
|
+
#
|
87
|
+
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
88
|
+
#
|
89
|
+
# can :read, Project, :active => true, :user_id => user.id
|
90
|
+
#
|
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.
|
93
|
+
#
|
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.
|
96
|
+
#
|
97
|
+
# can :update, Project do |project|
|
98
|
+
# project.groups.include?(user.group)
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# If the block returns true then the user has that :update ability for that project, otherwise he
|
102
|
+
# will be denied access. The downside to using a block is that it cannot be used to generate
|
103
|
+
# conditions for database queries.
|
104
|
+
#
|
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.
|
107
|
+
#
|
108
|
+
# can :read, :stats
|
109
|
+
# can? :read, :stats # => true
|
110
|
+
#
|
111
|
+
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
|
112
|
+
#
|
113
|
+
# can :update, Project, :priority => 3
|
114
|
+
# can? :update, Project # => true
|
115
|
+
#
|
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.
|
119
|
+
#
|
120
|
+
# can do |action, object_class, object|
|
121
|
+
# # check the database and return true/false
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
def can(action = nil, subject = nil, conditions = nil, &block)
|
125
|
+
rules << Rule.new(true, action, subject, conditions, block)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
129
|
+
#
|
130
|
+
# can :read, :all
|
131
|
+
# cannot :read, Comment
|
132
|
+
#
|
133
|
+
# A block can be passed just like "can", however if the logic is complex it is recommended
|
134
|
+
# to use the "can" method.
|
135
|
+
#
|
136
|
+
# cannot :read, Product do |product|
|
137
|
+
# product.invisible?
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
def cannot(action = nil, subject = nil, conditions = nil, &block)
|
141
|
+
rules << Rule.new(false, action, subject, conditions, block)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Alias one or more actions into another one.
|
145
|
+
#
|
146
|
+
# alias_action :update, :destroy, :to => :modify
|
147
|
+
# can :modify, Comment
|
148
|
+
#
|
149
|
+
# Then :modify permission will apply to both :update and :destroy requests.
|
150
|
+
#
|
151
|
+
# can? :update, Comment # => true
|
152
|
+
# can? :destroy, Comment # => true
|
153
|
+
#
|
154
|
+
# This only works in one direction. Passing the aliased action into the "can?" call
|
155
|
+
# will not work because aliases are meant to generate more generic actions.
|
156
|
+
#
|
157
|
+
# alias_action :update, :destroy, :to => :modify
|
158
|
+
# can :update, Comment
|
159
|
+
# can? :modify, Comment # => false
|
160
|
+
#
|
161
|
+
# Unless that exact alias is used.
|
162
|
+
#
|
163
|
+
# can :modify, Comment
|
164
|
+
# can? :modify, Comment # => true
|
165
|
+
#
|
166
|
+
# The following aliases are added by default for conveniently mapping common controller actions.
|
167
|
+
#
|
168
|
+
# alias_action :index, :show, :to => :read
|
169
|
+
# alias_action :new, :to => :create
|
170
|
+
# alias_action :edit, :to => :update
|
171
|
+
#
|
172
|
+
# This way one can use params[:action] in the controller to determine the permission.
|
173
|
+
def alias_action(*args)
|
174
|
+
target = args.pop[:to]
|
175
|
+
validate_target(target)
|
176
|
+
aliased_actions[target] ||= []
|
177
|
+
aliased_actions[target] += args
|
178
|
+
end
|
179
|
+
|
180
|
+
# User shouldn't specify targets with names of real actions or it will cause Seg fault
|
181
|
+
def validate_target(target)
|
182
|
+
raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
186
|
+
def aliased_actions
|
187
|
+
@aliased_actions ||= default_alias_actions
|
188
|
+
end
|
189
|
+
|
190
|
+
# Removes previously aliased actions including the defaults.
|
191
|
+
def clear_aliased_actions
|
192
|
+
@aliased_actions = {}
|
193
|
+
end
|
194
|
+
|
195
|
+
def model_adapter(model_class, action)
|
196
|
+
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
|
197
|
+
adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
|
198
|
+
end
|
199
|
+
|
200
|
+
# See ControllerAdditions#authorize! for documentation.
|
201
|
+
def authorize!(action, subject, *args)
|
202
|
+
message = nil
|
203
|
+
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
|
204
|
+
message = args.pop[:message]
|
205
|
+
end
|
206
|
+
if cannot?(action, subject, *args)
|
207
|
+
message ||= unauthorized_message(action, subject)
|
208
|
+
raise AccessDenied.new(message, action, subject)
|
209
|
+
end
|
210
|
+
subject
|
211
|
+
end
|
212
|
+
|
213
|
+
def unauthorized_message(action, subject)
|
214
|
+
keys = unauthorized_message_keys(action, subject)
|
215
|
+
variables = {:action => action.to_s}
|
216
|
+
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
217
|
+
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
218
|
+
message.blank? ? nil : message
|
219
|
+
end
|
220
|
+
|
221
|
+
def attributes_for(action, subject)
|
222
|
+
attributes = {}
|
223
|
+
relevant_rules(action, subject).map do |rule|
|
224
|
+
attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
|
225
|
+
end
|
226
|
+
attributes
|
227
|
+
end
|
228
|
+
|
229
|
+
def has_block?(action, subject)
|
230
|
+
relevant_rules(action, subject).any?(&:only_block?)
|
231
|
+
end
|
232
|
+
|
233
|
+
def has_raw_sql?(action, subject)
|
234
|
+
relevant_rules(action, subject).any?(&:only_raw_sql?)
|
235
|
+
end
|
236
|
+
|
237
|
+
def merge(ability)
|
238
|
+
ability.send(:rules).each do |rule|
|
239
|
+
rules << rule.dup
|
240
|
+
end
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def unauthorized_message_keys(action, subject)
|
247
|
+
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
|
248
|
+
[subject, :all].map do |try_subject|
|
249
|
+
[aliases_for_action(action), :manage].flatten.map do |try_action|
|
250
|
+
:"#{try_action}.#{try_subject}"
|
251
|
+
end
|
252
|
+
end.flatten
|
253
|
+
end
|
254
|
+
|
255
|
+
# Accepts an array of actions and returns an array of actions which match.
|
256
|
+
# This should be called before "matches?" and other checking methods since they
|
257
|
+
# rely on the actions to be expanded.
|
258
|
+
def expand_actions(actions)
|
259
|
+
expanded_actions[actions] ||= begin
|
260
|
+
expanded = []
|
261
|
+
actions.each do |action|
|
262
|
+
expanded << action
|
263
|
+
if aliases = aliased_actions[action]
|
264
|
+
expanded += expand_actions(aliases)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
expanded
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def expanded_actions
|
272
|
+
@expanded_actions ||= {}
|
273
|
+
end
|
274
|
+
|
275
|
+
# Given an action, it will try to find all of the actions which are aliased to it.
|
276
|
+
# This does the opposite kind of lookup as expand_actions.
|
277
|
+
def aliases_for_action(action)
|
278
|
+
results = [action]
|
279
|
+
aliased_actions.each do |aliased_action, actions|
|
280
|
+
results += aliases_for_action(aliased_action) if actions.include? action
|
281
|
+
end
|
282
|
+
results
|
283
|
+
end
|
284
|
+
|
285
|
+
def rules
|
286
|
+
@rules ||= []
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns an array of Rule instances which match the action and subject
|
290
|
+
# This does not take into consideration any hash conditions or block statements
|
291
|
+
def relevant_rules(action, subject)
|
292
|
+
relevant = rules.select do |rule|
|
293
|
+
rule.expanded_actions = expand_actions(rule.actions)
|
294
|
+
rule.relevant? action, subject
|
295
|
+
end
|
296
|
+
relevant.reverse!
|
297
|
+
relevant
|
298
|
+
end
|
299
|
+
|
300
|
+
def relevant_rules_for_match(action, subject)
|
301
|
+
relevant_rules(action, subject).each do |rule|
|
302
|
+
if rule.only_raw_sql?
|
303
|
+
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}"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def relevant_rules_for_query(action, subject)
|
309
|
+
relevant_rules(action, subject).each do |rule|
|
310
|
+
if rule.only_block?
|
311
|
+
raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def default_alias_actions
|
317
|
+
{
|
318
|
+
:read => [:index, :show],
|
319
|
+
:create => [:new],
|
320
|
+
:update => [:edit],
|
321
|
+
}
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|