Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e6bf4c8d12 | ||
|
68ea78b6e9 | ||
|
1f7e4c8b6b | ||
|
f1cebde51a | ||
|
9550154b09 | ||
|
17043ca61c | ||
|
aed9f26e56 | ||
|
6c1828acb6 | ||
|
6886aecb9a | ||
|
de000fdec7 | ||
|
76d465ae13 | ||
|
354e34b8ab | ||
|
245b83f6b4 | ||
|
78cbcf1db9 | ||
|
ccd24ab30f | ||
|
4986de8b3e | ||
|
0e8c7ca01f | ||
|
48ed6f9353 | ||
|
167d3832fc | ||
|
88cd11ba02 | ||
|
1ff1b70de4 | ||
|
8c72ab40a4 | ||
|
ec36137aa6 | ||
|
b37f2d083e | ||
|
c94de4ab18 | ||
|
6de9e4675a | ||
|
1fb2c0160c | ||
|
aa83fee7a4 | ||
|
eafd6cfcde | ||
|
67c93619c1 | ||
|
092b510a2f | ||
|
2160183e86 | ||
|
86063e4846 | ||
|
0442634d5a | ||
|
6c1d685f2c | ||
|
6ef2c44f57 | ||
|
b8ff2dbc6a | ||
|
f6c2054f7e | ||
|
a29e31606b | ||
|
e24d5d146b | ||
|
5a64d94d09 | ||
|
63865cc7d8 | ||
|
c6f9abb6ab | ||
|
e5b76210e4 | ||
|
baa1dacc21 | ||
|
f41b39406c | ||
|
27eba72e4b | ||
|
5d68caefd0 | ||
|
35fbee578f | ||
|
cf2896f011 | ||
|
bcac159b3e | ||
|
346ca2c74e | ||
|
242e912519 | ||
|
488cc2dfdd | ||
|
0f3753491d | ||
|
85efbdb8d0 | ||
|
a03d35272b | ||
|
7ee942c334 | ||
|
3a825ed0d1 | ||
|
98ed39264e |
|
@ -1 +1 @@
|
|||
1.8.7-p357
|
||||
1.9.3-p194
|
|
@ -1,7 +1,8 @@
|
|||
rvm:
|
||||
- 1.9.3
|
||||
- 1.9.2
|
||||
- 1.8.7
|
||||
- ree
|
||||
notifications:
|
||||
recipients:
|
||||
- graf.otodrakula@gmail.com
|
||||
- ryan@railscasts.com
|
||||
|
|
|
@ -1,26 +1,3 @@
|
|||
1.6.10 (May 7, 2013)
|
||||
|
||||
* fix matches_conditons_hash for string values on 1.8 (thanks rrosen)
|
||||
|
||||
* work around SQL injection vulnerability in older Rails versions (thanks steerio) - issue #800
|
||||
|
||||
* add support for nested join conditions (thanks yuszuv) - issue #806
|
||||
|
||||
* fix load_resource "find_by" in mongoid resources (thanks albertobajo) - issue #705
|
||||
|
||||
* fix namespace split behavior (thanks xinuc) - issue #668
|
||||
|
||||
1.6.9 (February 4, 2013)
|
||||
|
||||
* fix inserting AND (NULL) to end of SQL queries (thanks jonsgreen) - issue #687
|
||||
|
||||
* fix merge_joins for nested association hashes (thanks DavidMikeSimon) - issues #655, #560
|
||||
|
||||
* raise error on recursive alias_action (thanks fl00r) - issue #660
|
||||
|
||||
* fix namespace controllers not loading params (thanks andhapp) - issues #670, #664
|
||||
|
||||
|
||||
1.6.8 (June 25, 2012)
|
||||
|
||||
* improved support for namespaced controllers and models
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
### Please read before contributing
|
||||
|
||||
1) If you have any questions about CanCan, search the [Wiki](https://github.com/ryanb/cancan/wiki) or use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancan). Do not post questions here.
|
||||
|
||||
2) If you find a security bug, **DO NOT** submit an issue here. Please send an e-mail to [ryan@railscasts.com](mailto:ryan@railscasts.com) instead.
|
||||
|
||||
3) Do a small search on the issues tracker before submitting your issue to see if it was already reported / fixed. In case it was not, create your report including Rails and CanCan versions. If you are getting exceptions, please include the full backtrace.
|
||||
|
||||
That's it! The more information you give, the more easy it becomes for us to track it down and fix it. Ideal scenario would be adding the issue to CanCan test suite or to a sample application.
|
||||
|
||||
Thanks!
|
17
Gemfile
17
Gemfile
|
@ -1,20 +1,3 @@
|
|||
source "http://rubygems.org"
|
||||
|
||||
case ENV["MODEL_ADAPTER"]
|
||||
when nil, "active_record"
|
||||
gem "sqlite3"
|
||||
gem "activerecord", '~> 3.0.9', :require => "active_record"
|
||||
gem "with_model", "~> 0.2.5"
|
||||
gem "meta_where"
|
||||
when "data_mapper"
|
||||
gem "dm-core", "~> 1.0.2"
|
||||
gem "dm-sqlite-adapter", "~> 1.0.2"
|
||||
gem "dm-migrations", "~> 1.0.2"
|
||||
when "mongoid"
|
||||
gem "bson_ext", "~> 1.1"
|
||||
gem "mongoid", "~> 2.0.0.beta.20"
|
||||
else
|
||||
raise "Unknown model adapter: #{ENV["MODEL_ADAPTER"]}"
|
||||
end
|
||||
|
||||
gemspec
|
||||
|
|
144
README.rdoc
144
README.rdoc
|
@ -1,120 +1,108 @@
|
|||
= CanCan {<img src="https://fury-badge.herokuapp.com/rb/cancan.png" alt="Gem Version" />}[http://badge.fury.io/rb/cancan] {<img src="https://secure.travis-ci.org/ryanb/cancan.png?branch=master" />}[http://travis-ci.org/ryanb/cancan] {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/ryanb/cancan]
|
||||
= CanCan {<img src="https://secure.travis-ci.org/ryanb/cancan.png" />}[http://travis-ci.org/ryanb/cancan]
|
||||
|
||||
Wiki[https://github.com/ryanb/cancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
|
||||
This is the branch for CanCan 2.0 which is in very early development. For a stable release please check out the {master branch}[https://github.com/ryanb/cancan]
|
||||
|
||||
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.
|
||||
Here are some instructions for setting up CanCan 2.0. Try this out and provide feedback in the {issue tracker}[https://github.com/ryanb/cancan/issues].
|
||||
|
||||
|
||||
== Installation
|
||||
== Setup
|
||||
|
||||
In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
|
||||
CanCan expects your controllers to have a +current_user+ method. Add some authentication for this (such as Devise[https://github.com/plataformatec/devise], Authlogic[https://github.com/binarylogic/authlogic] or {nifty:authentication}[https://github.com/ryanb/nifty-generators]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/Changing-Defaults] to customize this behavior.
|
||||
|
||||
gem "cancan"
|
||||
To install CanCan, add it to your Gemfile and run the `bundle` command.
|
||||
|
||||
In <b>Rails 2</b>, add this to your environment.rb file.
|
||||
gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0"
|
||||
|
||||
config.gem "cancan"
|
||||
|
||||
Alternatively, you can install it as a plugin.
|
||||
|
||||
rails plugin install git://github.com/ryanb/cancan.git
|
||||
|
||||
|
||||
== Getting Started
|
||||
|
||||
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/ryanb/cancan/wiki/changing-defaults] if you need different behavior.
|
||||
|
||||
|
||||
=== 1. Define Abilities
|
||||
|
||||
User permissions are defined in an +Ability+ class. CanCan 1.5 includes a Rails 3 generator for creating this class.
|
||||
Next generate an Ability class, this is where your permissions will be defined.
|
||||
|
||||
rails g cancan:ability
|
||||
|
||||
In Rails 2.3, just add a new class in `app/models/ability.rb` with the folowing contents:
|
||||
Add authorization by calling {enable_authorization}[https://github.com/ryanb/cancan/wiki/enable_authorization] in your ApplicationController.
|
||||
|
||||
class Ability
|
||||
include CanCan::Ability
|
||||
|
||||
def initialize(user)
|
||||
end
|
||||
class ApplicationController < ActionController::Base
|
||||
enable_authorization
|
||||
end
|
||||
|
||||
See {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details.
|
||||
This will add an authorization check locking down every controller action. If you try visiting a page, a <tt>CanCan::Unauthorized</tt> exception will be raised since you have not granted the user ability to access it.
|
||||
|
||||
|
||||
=== 2. Check Abilities & Authorization
|
||||
== Defining Abilities
|
||||
|
||||
The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
|
||||
You grant access to controller actions through the +Ability+ class which was generated above. The +current_user+ is passed in allowing you to define permissions based on user attributes. For example:
|
||||
|
||||
<% if can? :update, @article %>
|
||||
<%= link_to "Edit", edit_article_path(@article) %>
|
||||
if user
|
||||
can :access, :all
|
||||
else
|
||||
can :access, :home
|
||||
can :create, [:users, :sessions]
|
||||
end
|
||||
|
||||
Here if there is a user he will be able to perform any action on any controller. If someone is not logged in he can only access the home, users, and sessions controllers.
|
||||
|
||||
The first argument to +can+ is the action the user can perform. The second argument is the controller name they can perform that action on. You can pass <tt>:access</tt> and <tt>:all</tt> to represent any action and controller respectively.
|
||||
|
||||
As shown above, pass an array to either of these will grant permission on each item in the array. It isn't necessary to pass the +new+ action here because CanCan includes some default aliases. See the {Aliases}[https://github.com/ryanb/cancan/wiki/Aliases] page for details.
|
||||
|
||||
You can check permissions in any controller or view using the <tt>can?</tt> method.
|
||||
|
||||
<% if can? :create, :comments %>
|
||||
<%= link_to "New Comment", new_comment_path %>
|
||||
<% end %>
|
||||
|
||||
See {Checking Abilities}[https://github.com/ryanb/cancan/wiki/checking-abilities] for more information
|
||||
Here the link will only show up the user can create comments.
|
||||
|
||||
The <tt>authorize!</tt> method in the controller will raise an exception if the user is not able to perform the given action.
|
||||
|
||||
def show
|
||||
@article = Article.find(params[:id])
|
||||
authorize! :read, @article
|
||||
== Resource Conditions
|
||||
|
||||
What if you need to change authorization based on a model's attributes? You can do so by passing a hash of conditions as the last argument to +can+. For example, if you want to only allow one to access projects which he owns you can set the <tt>:user_id</tt>.
|
||||
|
||||
can :access, :projects, :user_id => user.id
|
||||
|
||||
A block can also be used for complex condition checks just like in CanCan 1, but here it is not necessary.
|
||||
|
||||
If you try visiting any of the project pages at this point you will see a <tt>CanCan::InsufficientAuthorizationCheck</tt> exception is raised. This is because the default authorization has no way to check permissions on the <tt>@project</tt> instance. You can check permissions on an object manually using the <tt>authorize!</tt> method.
|
||||
|
||||
def edit
|
||||
@project = Project.find(params[:id])
|
||||
authorize! :edit, @project
|
||||
end
|
||||
|
||||
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.
|
||||
However this can get tedious. Instead CanCan provides a +load_and_authorize_resource+ method to load the <tt>@project</tt> instance in every controller action and authorize it.
|
||||
|
||||
class ArticlesController < ApplicationController
|
||||
class ProjectsController < ApplicationController
|
||||
load_and_authorize_resource
|
||||
|
||||
def show
|
||||
# @article is already loaded and authorized
|
||||
def edit
|
||||
# @project already loaded here and authorized
|
||||
end
|
||||
end
|
||||
|
||||
See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information.
|
||||
The +index+ (and other collection actions) will load the <tt>@projects</tt> instance which automatically limits the projects the user is allowed to access. This is a scope so you can make further calls to +where+ to limit what is returned from the database.
|
||||
|
||||
|
||||
=== 3. Handle Unauthorized Access
|
||||
|
||||
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+.
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
rescue_from CanCan::AccessDenied do |exception|
|
||||
redirect_to root_url, :alert => exception.message
|
||||
end
|
||||
def index
|
||||
@projects = @projects.where(:hidden => false)
|
||||
end
|
||||
|
||||
See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
|
||||
You can check permissions on instances using the <tt>can?</tt> method.
|
||||
|
||||
<%= link_to "Edit Project", edit_project_path if can? :update, @project %>
|
||||
|
||||
Here it will only show the edit link if the +user_id+ matches.
|
||||
|
||||
|
||||
=== 4. Lock It Down
|
||||
== Resource Attributes
|
||||
|
||||
If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
|
||||
In CanCan 2.0 it is possible to define permissions on specific resource attributes. For example, if you want to allow a user to only update the name and priority of a project, pass that as the third argument to +can+.
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
check_authorization
|
||||
end
|
||||
can :update, :projects, [:name, :priority]
|
||||
|
||||
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/ryanb/cancan/wiki/Ensure-Authorization] for more information.
|
||||
If you use this in combination with +load_and_authorize_resource+ it will ensure that only those two attributes exist in <tt>params[:project]</tt> when updating the project. If you do this everywhere it will not be necessary to use +attr_accessible+ in your models.
|
||||
|
||||
You can combine this with a hash of conditions. For example, here the user can update all attributes except the price when the product is discontinued.
|
||||
|
||||
== Wiki Docs
|
||||
can :update, :products
|
||||
cannot :update, :products, :price, :discontinued => true
|
||||
|
||||
* {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
|
||||
* {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
|
||||
* {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
|
||||
* {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
|
||||
* {Exception Handling}[https://github.com/ryanb/cancan/wiki/Exception-Handling]
|
||||
* {Changing Defaults}[https://github.com/ryanb/cancan/wiki/Changing-Defaults]
|
||||
* {See more}[https://github.com/ryanb/cancan/wiki]
|
||||
You can check permissions on specific attributes to determine what to show in the form.
|
||||
|
||||
<%= f.text_field :name if can? :update, @project, :name %>
|
||||
|
||||
== Questions or Problems?
|
||||
|
||||
If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/ryanb/cancan/wiki], please add an {issue on GitHub}[https://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
|
||||
|
||||
To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/ryanb/cancan/blob/master/spec/README.rdoc] for more information.
|
||||
|
||||
|
||||
== Special Thanks
|
||||
|
||||
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/ryanb/cancan/contributors]. See the CHANGELOG[https://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Gem::Specification.new do |s|
|
||||
s.name = "cancan"
|
||||
s.version = "1.6.10"
|
||||
s.version = "2.0.0.alpha"
|
||||
s.author = "Ryan Bates"
|
||||
s.email = "ryan@railscasts.com"
|
||||
s.homepage = "http://github.com/ryanb/cancan"
|
||||
|
@ -10,10 +10,16 @@ Gem::Specification.new do |s|
|
|||
s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"]
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency 'rspec', '~> 2.6.0'
|
||||
s.add_development_dependency 'rails', '~> 3.0.9'
|
||||
s.add_development_dependency 'rr', '~> 0.10.11' # 1.0.0 has respond_to? issues: http://github.com/btakita/rr/issues/issue/43
|
||||
s.add_development_dependency 'supermodel', '~> 0.1.4'
|
||||
s.add_development_dependency "rspec", "~> 2.9.0"
|
||||
s.add_development_dependency "rails", "~> 3.2.6"
|
||||
s.add_development_dependency "sqlite3", "~> 1.3.5"
|
||||
|
||||
s.add_development_dependency "dm-core", "~> 1.2.0"
|
||||
s.add_development_dependency "dm-sqlite-adapter", "~> 1.2.0"
|
||||
s.add_development_dependency "dm-migrations", "~> 1.2.0"
|
||||
|
||||
s.add_development_dependency "mongoid", "~> 2.4.8"
|
||||
s.add_development_dependency "bson_ext", "~> 1.6.2"
|
||||
|
||||
s.rubyforge_project = s.name
|
||||
s.required_rubygems_version = ">= 1.3.4"
|
||||
|
|
|
@ -8,7 +8,7 @@ module CanCan
|
|||
#
|
||||
# def initialize(user)
|
||||
# if user.admin?
|
||||
# can :manage, :all
|
||||
# can :access, :all
|
||||
# else
|
||||
# can :read, :all
|
||||
# end
|
||||
|
@ -22,21 +22,12 @@ module CanCan
|
|||
#
|
||||
# You can also pass the class instead of an instance (if you don't have one handy).
|
||||
#
|
||||
# can? :create, Project
|
||||
# can? :create, :projects
|
||||
#
|
||||
# Nested resources can be passed through a hash, this way conditions which are
|
||||
# dependent upon the association will work when using a class.
|
||||
#
|
||||
# can? :create, @category => Project
|
||||
#
|
||||
# Any additional arguments will be passed into the "can" block definition. This
|
||||
# can be used to pass more information about the user's request for example.
|
||||
#
|
||||
# can? :create, Project, request.remote_ip
|
||||
#
|
||||
# can :create Project do |project, remote_ip|
|
||||
# # ...
|
||||
# end
|
||||
# can? :create, @category => :projects
|
||||
#
|
||||
# Not only can you use the can? method in the controller and view (see ControllerAdditions),
|
||||
# but you can also call it directly on an ability instance.
|
||||
|
@ -53,9 +44,9 @@ module CanCan
|
|||
# end
|
||||
#
|
||||
# Also see the RSpec Matchers to aid in testing.
|
||||
def can?(action, subject, *extra_args)
|
||||
match = relevant_rules_for_match(action, subject).detect do |rule|
|
||||
rule.matches_conditions?(action, subject, extra_args)
|
||||
def can?(action, subject, attribute = nil)
|
||||
match = relevant_rules_for_match(action, subject, attribute).detect do |rule|
|
||||
rule.matches_conditions?(action, subject, attribute)
|
||||
end
|
||||
match ? match.base_behavior : false
|
||||
end
|
||||
|
@ -71,22 +62,22 @@ module CanCan
|
|||
# Defines which abilities are allowed using 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.
|
||||
#
|
||||
# can :update, Article
|
||||
# can :update, :articles
|
||||
#
|
||||
# You can pass an array for either of these parameters to match any one.
|
||||
# Here the user has the ability to update or destroy both articles and comments.
|
||||
#
|
||||
# can [:update, :destroy], [Article, Comment]
|
||||
# can [:update, :destroy], [:articles, :comments]
|
||||
#
|
||||
# You can pass :all to match any object and :manage to match any action. Here are some examples.
|
||||
# You can pass :all to match any object and :access to match any action. Here are some examples.
|
||||
#
|
||||
# can :manage, :all
|
||||
# can :access, :all
|
||||
# can :update, :all
|
||||
# can :manage, Project
|
||||
# can :access, :projects
|
||||
#
|
||||
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
||||
#
|
||||
# can :read, Project, :active => true, :user_id => user.id
|
||||
# can :read, :projects, :active => true, :user_id => user.id
|
||||
#
|
||||
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
|
||||
# are also used for initial attributes when building a record in ControllerAdditions#load_resource.
|
||||
|
@ -94,7 +85,7 @@ module CanCan
|
|||
# If the conditions hash does not give you enough control over defining abilities, you can use a block
|
||||
# along with any Ruby code you want.
|
||||
#
|
||||
# can :update, Project do |project|
|
||||
# can :update, :projects do |project|
|
||||
# project.groups.include?(user.group)
|
||||
# end
|
||||
#
|
||||
|
@ -102,27 +93,21 @@ module CanCan
|
|||
# will be denied access. The downside to using a block is that it cannot be used to generate
|
||||
# conditions for database queries.
|
||||
#
|
||||
# You can pass custom objects into this "can" method, this is usually done with a symbol
|
||||
# and is useful if a class isn't available to define permissions on.
|
||||
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol.
|
||||
#
|
||||
# can :read, :stats
|
||||
# can? :read, :stats # => true
|
||||
#
|
||||
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
|
||||
#
|
||||
# can :update, Project, :priority => 3
|
||||
# can? :update, Project # => true
|
||||
# can :update, :projects, :priority => 3
|
||||
# can? :update, :projects # => true
|
||||
#
|
||||
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
|
||||
# block will always be executed. This allows you to override the full behavior if the permissions are
|
||||
# defined in an external source such as the database.
|
||||
#
|
||||
# can do |action, object_class, object|
|
||||
# can do |action, subject, object|
|
||||
# # check the database and return true/false
|
||||
# end
|
||||
#
|
||||
def can(action = nil, subject = nil, conditions = nil, &block)
|
||||
rules << Rule.new(true, action, subject, conditions, block)
|
||||
def can(*args, &block)
|
||||
rules << Rule.new(true, *args, &block)
|
||||
end
|
||||
|
||||
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
||||
|
@ -133,35 +118,30 @@ module CanCan
|
|||
# A block can be passed just like "can", however if the logic is complex it is recommended
|
||||
# to use the "can" method.
|
||||
#
|
||||
# cannot :read, Product do |product|
|
||||
# cannot :read, :projects do |product|
|
||||
# product.invisible?
|
||||
# end
|
||||
#
|
||||
def cannot(action = nil, subject = nil, conditions = nil, &block)
|
||||
rules << Rule.new(false, action, subject, conditions, block)
|
||||
def cannot(*args, &block)
|
||||
rules << Rule.new(false, *args, &block)
|
||||
end
|
||||
|
||||
# Alias one or more actions into another one.
|
||||
#
|
||||
# alias_action :update, :destroy, :to => :modify
|
||||
# can :modify, Comment
|
||||
# can :modify, :comments
|
||||
#
|
||||
# Then :modify permission will apply to both :update and :destroy requests.
|
||||
#
|
||||
# can? :update, Comment # => true
|
||||
# can? :destroy, Comment # => true
|
||||
# can? :update, :comments # => true
|
||||
# can? :destroy, :comments # => true
|
||||
#
|
||||
# This only works in one direction. Passing the aliased action into the "can?" call
|
||||
# will not work because aliases are meant to generate more generic actions.
|
||||
#
|
||||
# alias_action :update, :destroy, :to => :modify
|
||||
# can :update, Comment
|
||||
# can? :modify, Comment # => false
|
||||
#
|
||||
# Unless that exact alias is used.
|
||||
#
|
||||
# can :modify, Comment
|
||||
# can? :modify, Comment # => true
|
||||
# can :update, :comments
|
||||
# can? :modify, :comments # => false
|
||||
#
|
||||
# The following aliases are added by default for conveniently mapping common controller actions.
|
||||
#
|
||||
|
@ -172,40 +152,61 @@ module CanCan
|
|||
# This way one can use params[:action] in the controller to determine the permission.
|
||||
def alias_action(*args)
|
||||
target = args.pop[:to]
|
||||
validate_target(target)
|
||||
aliased_actions[target] ||= []
|
||||
aliased_actions[target] += args
|
||||
aliases[:actions][target] ||= []
|
||||
aliases[:actions][target] += args
|
||||
end
|
||||
|
||||
# User shouldn't specify targets with names of real actions or it will cause Seg fault
|
||||
def validate_target(target)
|
||||
raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
|
||||
# Alias one or more subjects into another one.
|
||||
#
|
||||
# alias_subject :admins, :moderators, :to => :users
|
||||
# can :update, :users
|
||||
#
|
||||
# Then :modify permission will apply to both :update and :destroy requests.
|
||||
#
|
||||
# can? :update, :admins # => true
|
||||
# can? :update, :moderators # => true
|
||||
#
|
||||
# This only works in one direction. Passing the aliased subject into the "can?" call
|
||||
# will not work because aliases are meant to generate more generic subjects.
|
||||
#
|
||||
# alias_subject :admins, :moderators, :to => :users
|
||||
# can :update, :admins
|
||||
# can? :update, :users # => false
|
||||
#
|
||||
def alias_subject(*args)
|
||||
target = args.pop[:to]
|
||||
aliases[:subjects][target] ||= []
|
||||
aliases[:subjects][target] += args
|
||||
end
|
||||
|
||||
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
||||
def aliased_actions
|
||||
@aliased_actions ||= default_alias_actions
|
||||
# Returns a hash of action and subject aliases.
|
||||
def aliases
|
||||
@aliases ||= default_aliases
|
||||
end
|
||||
|
||||
# Removes previously aliased actions including the defaults.
|
||||
def clear_aliased_actions
|
||||
@aliased_actions = {}
|
||||
# Removes previously aliased actions or subjects including the defaults.
|
||||
def clear_aliases
|
||||
aliases[:actions] = {}
|
||||
aliases[:subjects] = {}
|
||||
end
|
||||
|
||||
def model_adapter(model_class, action)
|
||||
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
|
||||
adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
|
||||
adapter_class.new(model_class, relevant_rules_for_query(action, model_class.to_s.underscore.pluralize.to_sym))
|
||||
end
|
||||
|
||||
# See ControllerAdditions#authorize! for documentation.
|
||||
def authorize!(action, subject, *args)
|
||||
message = nil
|
||||
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
|
||||
if args.last.kind_of?(Hash)
|
||||
message = args.pop[:message]
|
||||
end
|
||||
attribute = args.first
|
||||
if cannot?(action, subject, *args)
|
||||
message ||= unauthorized_message(action, subject)
|
||||
raise AccessDenied.new(message, action, subject)
|
||||
raise Unauthorized.new(message, action, subject)
|
||||
elsif sufficient_attribute_check?(action, subject, attribute) && sufficient_condition_check?(action, subject)
|
||||
fully_authorized!(action, subject)
|
||||
end
|
||||
subject
|
||||
end
|
||||
|
@ -213,7 +214,7 @@ module CanCan
|
|||
def unauthorized_message(action, subject)
|
||||
keys = unauthorized_message_keys(action, subject)
|
||||
variables = {:action => action.to_s}
|
||||
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
||||
variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
|
||||
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
||||
message.blank? ? nil : message
|
||||
end
|
||||
|
@ -234,6 +235,25 @@ module CanCan
|
|||
relevant_rules(action, subject).any?(&:only_raw_sql?)
|
||||
end
|
||||
|
||||
def has_instance_conditions?(action, subject)
|
||||
relevant_rules(action, subject).any?(&:instance_conditions?)
|
||||
end
|
||||
|
||||
def has_attributes?(action, subject)
|
||||
relevant_rules(action, subject).any?(&:attributes?)
|
||||
end
|
||||
|
||||
def fully_authorized?(action, subject)
|
||||
@fully_authorized ||= []
|
||||
@fully_authorized.include? [action.to_sym, subject.to_sym]
|
||||
end
|
||||
|
||||
def fully_authorized!(action, subject)
|
||||
subject = subject.class.to_s.underscore.pluralize.to_sym unless subject.kind_of?(Symbol) || subject.kind_of?(String)
|
||||
@fully_authorized ||= []
|
||||
@fully_authorized << [action.to_sym, subject.to_sym]
|
||||
end
|
||||
|
||||
def merge(ability)
|
||||
ability.send(:rules).each do |rule|
|
||||
rules << rule.dup
|
||||
|
@ -244,29 +264,37 @@ module CanCan
|
|||
private
|
||||
|
||||
def unauthorized_message_keys(action, subject)
|
||||
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
|
||||
[subject, :all].map do |try_subject|
|
||||
[aliases_for_action(action), :manage].flatten.map do |try_action|
|
||||
subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.pluralize)
|
||||
[aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject|
|
||||
[aliases_for(:actions, action.to_sym), :access].flatten.map do |try_action|
|
||||
:"#{try_action}.#{try_subject}"
|
||||
end
|
||||
end.flatten
|
||||
end
|
||||
|
||||
def sufficient_attribute_check?(action, subject, attribute)
|
||||
!(%w[create update].include?(action.to_s) && attribute.nil? && has_attributes?(action, subject))
|
||||
end
|
||||
|
||||
def sufficient_condition_check?(action, subject)
|
||||
!((subject.kind_of?(Symbol) || subject.kind_of?(String)) && has_instance_conditions?(action, subject))
|
||||
end
|
||||
|
||||
# Accepts an array of actions and returns an array of actions which match.
|
||||
# This should be called before "matches?" and other checking methods since they
|
||||
# rely on the actions to be expanded.
|
||||
def expand_actions(actions)
|
||||
actions.map do |action|
|
||||
aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
|
||||
def expand_aliases(type, items)
|
||||
items.map do |item|
|
||||
aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item
|
||||
end.flatten
|
||||
end
|
||||
|
||||
# Given an action, it will try to find all of the actions which are aliased to it.
|
||||
# This does the opposite kind of lookup as expand_actions.
|
||||
def aliases_for_action(action)
|
||||
# This does the opposite kind of lookup as expand_aliases.
|
||||
def aliases_for(type, action)
|
||||
results = [action]
|
||||
aliased_actions.each do |aliased_action, actions|
|
||||
results += aliases_for_action(aliased_action) if actions.include? action
|
||||
aliases[type].each do |aliased_action, actions|
|
||||
results += aliases_for(type, aliased_action) if actions.include? action
|
||||
end
|
||||
results
|
||||
end
|
||||
|
@ -277,15 +305,20 @@ module CanCan
|
|||
|
||||
# Returns an array of Rule instances which match the action and subject
|
||||
# This does not take into consideration any hash conditions or block statements
|
||||
def relevant_rules(action, subject)
|
||||
rules.reverse.select do |rule|
|
||||
rule.expanded_actions = expand_actions(rule.actions)
|
||||
rule.relevant? action, subject
|
||||
def relevant_rules(action, subject, attribute = nil)
|
||||
specificity = 0
|
||||
rules.reverse.each_with_object([]) do |rule, relevant_rules|
|
||||
rule.expanded_actions = expand_aliases(:actions, rule.actions)
|
||||
rule.expanded_subjects = expand_aliases(:subjects, rule.subjects)
|
||||
if rule.relevant?(action, subject, attribute) && rule.specificity >= specificity
|
||||
specificity = rule.specificity if rule.base_behavior
|
||||
relevant_rules << rule
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def relevant_rules_for_match(action, subject)
|
||||
relevant_rules(action, subject).each do |rule|
|
||||
def relevant_rules_for_match(action, subject, attribute)
|
||||
relevant_rules(action, subject, attribute).each do |rule|
|
||||
if rule.only_raw_sql?
|
||||
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}"
|
||||
end
|
||||
|
@ -293,18 +326,22 @@ module CanCan
|
|||
end
|
||||
|
||||
def relevant_rules_for_query(action, subject)
|
||||
relevant_rules(action, subject).each do |rule|
|
||||
relevant_rules(action, subject, nil).each do |rule|
|
||||
if rule.only_block?
|
||||
raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def default_alias_actions
|
||||
def default_aliases
|
||||
{
|
||||
:subjects => {},
|
||||
:actions => {
|
||||
:read => [:index, :show],
|
||||
:create => [:new],
|
||||
:update => [:edit],
|
||||
:destroy => [:delete],
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module CanCan
|
|||
# end
|
||||
#
|
||||
def load_and_authorize_resource(*args)
|
||||
cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
|
||||
cancan_resource_class.add_before_filter(self, {:load => true, :authorize => true}, *args)
|
||||
end
|
||||
|
||||
# Sets up a before filter which loads the model resource into an instance variable.
|
||||
|
@ -96,11 +96,6 @@ module CanCan
|
|||
#
|
||||
# load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
|
||||
#
|
||||
# [:+id_param+]
|
||||
# Find using a param key other than :id. For example:
|
||||
#
|
||||
# load_resource :id_key => :url # will use find(params[:url])
|
||||
#
|
||||
# [:+collection+]
|
||||
# Specify which actions are resource collection actions in addition to :+index+. This
|
||||
# is usually not necessary because it will try to guess depending on if the id param is present.
|
||||
|
@ -118,7 +113,8 @@ module CanCan
|
|||
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
||||
#
|
||||
def load_resource(*args)
|
||||
cancan_resource_class.add_before_filter(self, :load_resource, *args)
|
||||
raise ImplementationRemoved, "The load_resource method has been removed, use load_and_authorize_resource instead."
|
||||
cancan_resource_class.add_before_filter(self, {:load => true}, *args)
|
||||
end
|
||||
|
||||
# Sets up a before filter which authorizes the resource using the instance variable.
|
||||
|
@ -177,7 +173,8 @@ module CanCan
|
|||
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
||||
#
|
||||
def authorize_resource(*args)
|
||||
cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
|
||||
raise ImplementationRemoved, "The authorize_resource method has been removed, use load_and_authorize_resource instead."
|
||||
cancan_resource_class.add_before_filter(self, {:authorize => true}, *args)
|
||||
end
|
||||
|
||||
# Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
|
||||
|
@ -205,6 +202,7 @@ module CanCan
|
|||
#
|
||||
# You can also pass the resource name as the first argument to skip that resource.
|
||||
def skip_load_resource(*args)
|
||||
raise ImplementationRemoved, "The skip_load_resource method has been removed, use skip_load_and_authorize_resource instead."
|
||||
options = args.extract_options!
|
||||
name = args.first
|
||||
cancan_skipper[:load][name] = options
|
||||
|
@ -221,20 +219,23 @@ module CanCan
|
|||
#
|
||||
# You can also pass the resource name as the first argument to skip that resource.
|
||||
def skip_authorize_resource(*args)
|
||||
raise ImplementationRemoved, "The skip_authorize_resource method has been removed, use skip_load_and_authorize_resource instead."
|
||||
options = args.extract_options!
|
||||
name = args.first
|
||||
cancan_skipper[:authorize][name] = options
|
||||
end
|
||||
|
||||
# Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
|
||||
# If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised.
|
||||
# This is normally added to the ApplicationController to ensure all controller actions do authorization.
|
||||
# Add this to a controller to automatically perform authorization on every action.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# check_authorization
|
||||
# enable_authorization
|
||||
# end
|
||||
#
|
||||
# See skip_authorization_check to bypass this check on specific controller actions.
|
||||
# Internally it does this in a before_filter for every action.
|
||||
#
|
||||
# authorize! params[:action], params[:controller]
|
||||
#
|
||||
# If you need to "skip" authorization in a given controller, it is best to enable :+access+ to it in the +Ability+.
|
||||
#
|
||||
# Options:
|
||||
# [:+only+]
|
||||
|
@ -244,39 +245,29 @@ module CanCan
|
|||
# Does not apply to given actions.
|
||||
#
|
||||
# [:+if+]
|
||||
# Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
|
||||
# Supply the name of a controller method to be called. The authorization only takes place if this returns true.
|
||||
#
|
||||
# check_authorization :if => :admin_controller?
|
||||
# enable_authorization :if => :admin_controller?
|
||||
#
|
||||
# [:+unless+]
|
||||
# Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
|
||||
# Supply the name of a controller method to be called. The authorization only takes place if this returns false.
|
||||
#
|
||||
# check_authorization :unless => :devise_controller?
|
||||
# enable_authorization :unless => :devise_controller?
|
||||
#
|
||||
def check_authorization(options = {})
|
||||
self.after_filter(options.slice(:only, :except)) do |controller|
|
||||
next if controller.instance_variable_defined?(:@_authorized)
|
||||
next if options[:if] && !controller.send(options[:if])
|
||||
next if options[:unless] && controller.send(options[:unless])
|
||||
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
|
||||
def enable_authorization(options = {}, &block)
|
||||
before_filter(options.slice(:only, :except)) do |controller|
|
||||
break if options[:if] && !controller.send(options[:if])
|
||||
break if options[:unless] && controller.send(options[:unless])
|
||||
controller.authorize! controller.params[:action], controller.params[:controller]
|
||||
end
|
||||
after_filter(options.slice(:only, :except)) do |controller|
|
||||
break if options[:if] && !controller.send(options[:if])
|
||||
break if options[:unless] && controller.send(options[:unless])
|
||||
unless controller.current_ability.fully_authorized? controller.params[:action], controller.params[:controller]
|
||||
raise CanCan::InsufficientAuthorizationCheck, "Authorization check is not sufficient for this action. This is probably because you have conditions or attributes defined in Ability and are not checking for them in the action. One way to solve this is adding load_and_authorize_resource to this controller."
|
||||
end
|
||||
end
|
||||
|
||||
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
|
||||
#
|
||||
# class HomeController < ApplicationController
|
||||
# skip_authorization_check :only => :index
|
||||
# end
|
||||
#
|
||||
# Any arguments are passed to the +before_filter+ it triggers.
|
||||
def skip_authorization_check(*args)
|
||||
self.before_filter(*args) do |controller|
|
||||
controller.instance_variable_set(:@_authorized, true)
|
||||
end
|
||||
end
|
||||
|
||||
def skip_authorization(*args)
|
||||
raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
|
||||
rescue_from(CanCan::Unauthorized, &block) if block
|
||||
end
|
||||
|
||||
def cancan_resource_class
|
||||
|
@ -287,8 +278,16 @@ module CanCan
|
|||
end
|
||||
end
|
||||
|
||||
def check_authorization(options = {})
|
||||
raise ImplementationRemoved, "The check_authorization method has been removed, use enable_authorization instead."
|
||||
end
|
||||
|
||||
def skip_authorization_check(*args)
|
||||
raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
|
||||
end
|
||||
|
||||
def cancan_skipper
|
||||
@_cancan_skipper ||= {:authorize => {}, :load => {}}
|
||||
raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -297,7 +296,7 @@ module CanCan
|
|||
base.helper_method :can?, :cannot?, :current_ability
|
||||
end
|
||||
|
||||
# Raises a CanCan::AccessDenied exception if the current_ability cannot
|
||||
# Raises a CanCan::Unauthorized exception if the current_ability cannot
|
||||
# perform the given action. This is usually called in a controller action or
|
||||
# before filter to perform the authorization.
|
||||
#
|
||||
|
@ -324,12 +323,12 @@ module CanCan
|
|||
# access is displayed to the user.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# rescue_from CanCan::AccessDenied do |exception|
|
||||
# rescue_from CanCan::Unauthorized do |exception|
|
||||
# redirect_to root_url, :alert => exception.message
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# See the CanCan::AccessDenied exception for more details on working with the exception.
|
||||
# See the CanCan::Unauthorized exception for more details on working with the exception.
|
||||
#
|
||||
# See the load_and_authorize_resource method to automatically add the authorize! behavior
|
||||
# to the default RESTful actions.
|
||||
|
@ -338,10 +337,6 @@ module CanCan
|
|||
current_ability.authorize!(*args)
|
||||
end
|
||||
|
||||
def unauthorized!(message = nil)
|
||||
raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
|
||||
end
|
||||
|
||||
# Creates and returns the current user's ability and caches it. If you
|
||||
# want to override how the Ability is defined then this is the place.
|
||||
# Just define the method in the controller to change behavior.
|
||||
|
|
|
@ -2,12 +2,12 @@ module CanCan
|
|||
# Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
|
||||
# This class is used internally, so you do not need to call methods directly on it.
|
||||
class ControllerResource # :nodoc:
|
||||
def self.add_before_filter(controller_class, method, *args)
|
||||
options = args.extract_options!
|
||||
def self.add_before_filter(controller_class, behavior, *args)
|
||||
options = args.extract_options!.merge(behavior)
|
||||
resource_name = args.first
|
||||
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
|
||||
controller_class.send(before_filter_method, options.slice(:only, :except, :if, :unless)) do |controller|
|
||||
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
|
||||
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).process
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -16,33 +16,27 @@ module CanCan
|
|||
@params = controller.params
|
||||
@options = args.extract_options!
|
||||
@name = args.first
|
||||
raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
|
||||
raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
|
||||
raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
|
||||
end
|
||||
|
||||
def load_and_authorize_resource
|
||||
load_resource
|
||||
authorize_resource
|
||||
if update_actions.include?(@params[:action].to_sym)
|
||||
update_resource
|
||||
authorize_resource # Reauthorize the now-updated resource
|
||||
end
|
||||
end
|
||||
|
||||
def load_resource
|
||||
unless skip?(:load)
|
||||
def process
|
||||
if @options[:load]
|
||||
if load_instance?
|
||||
self.resource_instance ||= load_resource_instance
|
||||
elsif load_collection?
|
||||
self.collection_instance ||= load_collection
|
||||
current_ability.fully_authorized! @params[:action], @params[:controller]
|
||||
end
|
||||
end
|
||||
if @options[:authorize]
|
||||
if resource_instance
|
||||
if resource_params && (authorization_action == :create || authorization_action == :update)
|
||||
resource_params.each do |key, value|
|
||||
@controller.authorize!(authorization_action, resource_instance, key.to_sym)
|
||||
end
|
||||
else
|
||||
@controller.authorize!(authorization_action, resource_instance)
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_resource
|
||||
unless skip?(:authorize)
|
||||
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -50,18 +44,18 @@ module CanCan
|
|||
@options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
|
||||
end
|
||||
|
||||
def skip?(behavior) # This could probably use some refactoring
|
||||
options = @controller.class.cancan_skipper[behavior][@name]
|
||||
if options.nil?
|
||||
false
|
||||
elsif options == {}
|
||||
true
|
||||
elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
|
||||
true
|
||||
elsif [options[:only]].flatten.include?(@params[:action].to_sym)
|
||||
true
|
||||
end
|
||||
end
|
||||
# def skip?(behavior) # This could probably use some refactoring
|
||||
# options = @controller.class.cancan_skipper[behavior][@name]
|
||||
# if options.nil?
|
||||
# false
|
||||
# elsif options == {}
|
||||
# true
|
||||
# elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
|
||||
# true
|
||||
# elsif [options[:only]].flatten.include?(@params[:action].to_sym)
|
||||
# true
|
||||
# end
|
||||
# end
|
||||
|
||||
protected
|
||||
|
||||
|
@ -69,7 +63,7 @@ module CanCan
|
|||
if !parent? && new_actions.include?(@params[:action].to_sym)
|
||||
build_resource
|
||||
elsif id_param || @options[:singleton]
|
||||
find_resource
|
||||
find_and_update_resource
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -78,7 +72,7 @@ module CanCan
|
|||
end
|
||||
|
||||
def load_collection?
|
||||
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
|
||||
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, subject_name)
|
||||
end
|
||||
|
||||
def load_collection
|
||||
|
@ -90,24 +84,30 @@ module CanCan
|
|||
assign_attributes(resource)
|
||||
end
|
||||
|
||||
def update_resource
|
||||
resource.assign_attributes(resource_params || {})
|
||||
end
|
||||
|
||||
def assign_attributes(resource)
|
||||
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
||||
initial_attributes.each do |attr_name, value|
|
||||
resource.send("#{attr_name}=", value)
|
||||
end
|
||||
|
||||
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
||||
resource
|
||||
end
|
||||
|
||||
def initial_attributes
|
||||
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
|
||||
current_ability.attributes_for(@params[:action].to_sym, subject_name).delete_if do |key, value|
|
||||
resource_params && resource_params.include?(key)
|
||||
end
|
||||
end
|
||||
|
||||
def find_and_update_resource
|
||||
resource = find_resource
|
||||
if resource_params
|
||||
@controller.authorize!(authorization_action, resource) if @options[:authorize]
|
||||
resource.attributes = resource_params
|
||||
end
|
||||
resource
|
||||
end
|
||||
|
||||
def find_resource
|
||||
if @options[:singleton] && parent_resource.respond_to?(name)
|
||||
parent_resource.send(name)
|
||||
|
@ -115,8 +115,6 @@ module CanCan
|
|||
if @options[:find_by]
|
||||
if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
|
||||
resource_base.send("find_by_#{@options[:find_by]}!", id_param)
|
||||
elsif resource_base.respond_to? "find_by"
|
||||
resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
|
||||
else
|
||||
resource_base.send(@options[:find_by], id_param)
|
||||
end
|
||||
|
@ -139,7 +137,7 @@ module CanCan
|
|||
@params[@options[:id_param]]
|
||||
else
|
||||
@params[parent? ? :"#{name}_id" : :id]
|
||||
end.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def member_action?
|
||||
|
@ -158,8 +156,12 @@ module CanCan
|
|||
end
|
||||
end
|
||||
|
||||
def resource_class_with_parent
|
||||
parent_resource ? {parent_resource => resource_class} : resource_class
|
||||
def subject_name
|
||||
resource_class.to_s.underscore.pluralize.to_sym
|
||||
end
|
||||
|
||||
def subject_name_with_parent
|
||||
parent_resource ? {parent_resource => subject_name} : subject_name
|
||||
end
|
||||
|
||||
def resource_instance=(instance)
|
||||
|
@ -167,7 +169,13 @@ module CanCan
|
|||
end
|
||||
|
||||
def resource_instance
|
||||
@controller.instance_variable_get("@#{instance_name}") if load_instance?
|
||||
if load_instance?
|
||||
if @controller.instance_variable_defined? "@#{instance_name}"
|
||||
@controller.instance_variable_get("@#{instance_name}")
|
||||
elsif @controller.respond_to?(instance_name, true)
|
||||
@controller.send(instance_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def collection_instance=(instance)
|
||||
|
@ -189,7 +197,7 @@ module CanCan
|
|||
elsif @options[:shallow]
|
||||
resource_class
|
||||
else
|
||||
raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
|
||||
raise Unauthorized.new(nil, authorization_action, @params[:controller].to_sym) # maybe this should be a record not found error instead?
|
||||
end
|
||||
else
|
||||
resource_class
|
||||
|
@ -223,15 +231,10 @@ module CanCan
|
|||
|
||||
def resource_params
|
||||
if @options[:class]
|
||||
params_key = extract_key(@options[:class])
|
||||
return @params[params_key] if @params[params_key]
|
||||
@params[@options[:class].to_s.underscore.gsub('/', '_')]
|
||||
else
|
||||
@params[namespaced_name.to_s.underscore.gsub("/", "_")]
|
||||
end
|
||||
|
||||
resource_params_by_namespaced_name
|
||||
end
|
||||
|
||||
def resource_params_by_namespaced_name
|
||||
@params[extract_key(namespaced_name)]
|
||||
end
|
||||
|
||||
def namespace
|
||||
|
@ -259,15 +262,5 @@ module CanCan
|
|||
def new_actions
|
||||
[:new, :create] + [@options[:new]].flatten
|
||||
end
|
||||
|
||||
def update_actions
|
||||
[:update] #TODO: check what @options is for
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_key(value)
|
||||
value.to_s.underscore.gsub('/', '_')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,11 +11,14 @@ module CanCan
|
|||
# Raised when using check_authorization without calling authorized!
|
||||
class AuthorizationNotPerformed < Error; end
|
||||
|
||||
# Raised when enable_authorization is used and not fully authorized by the end of the action
|
||||
class InsufficientAuthorizationCheck < Error; end
|
||||
|
||||
# This error is raised when a user isn't allowed to access a given controller action.
|
||||
# This usually happens within a call to ControllerAdditions#authorize! but can be
|
||||
# raised manually.
|
||||
#
|
||||
# raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
|
||||
# raise CanCan::Unauthorized.new("Not authorized!", :read, Article)
|
||||
#
|
||||
# The passed message, action, and subject are optional and can later be retrieved when
|
||||
# rescuing from the exception.
|
||||
|
@ -30,9 +33,9 @@ module CanCan
|
|||
# exception.default_message = "Default error message"
|
||||
# exception.message # => "Default error message"
|
||||
#
|
||||
# See ControllerAdditions#authorized! for more information on rescuing from this exception
|
||||
# See ControllerAdditions#authorize! for more information on rescuing from this exception
|
||||
# and customizing the message using I18n.
|
||||
class AccessDenied < Error
|
||||
class Unauthorized < Error
|
||||
attr_reader :action, :subject
|
||||
attr_writer :default_message
|
||||
|
||||
|
|
|
@ -66,22 +66,11 @@ module CanCan
|
|||
return conditions unless conditions.kind_of? Hash
|
||||
conditions.inject({}) do |result_hash, (name, value)|
|
||||
if value.kind_of? Hash
|
||||
value = value.dup
|
||||
association_class = model_class.reflect_on_association(name).class_name.constantize
|
||||
nested = value.inject({}) do |nested,(k,v)|
|
||||
if v.kind_of? Hash
|
||||
value.delete(k)
|
||||
nested[k] = v
|
||||
else
|
||||
name = model_class.reflect_on_association(name).table_name.to_sym
|
||||
value = tableized_conditions(value, association_class)
|
||||
end
|
||||
result_hash[name] = value
|
||||
end
|
||||
nested
|
||||
end
|
||||
result_hash.merge!(tableized_conditions(nested,association_class))
|
||||
else
|
||||
result_hash[name] = value
|
||||
end
|
||||
result_hash
|
||||
end
|
||||
end
|
||||
|
@ -156,8 +145,8 @@ module CanCan
|
|||
# Takes two hashes and does a deep merge.
|
||||
def merge_joins(base, add)
|
||||
add.each do |name, nested|
|
||||
if base[name].is_a?(Hash)
|
||||
merge_joins(base[name], nested) unless nested.empty?
|
||||
if base[name].is_a?(Hash) && !nested.empty?
|
||||
merge_joins(base[name], nested)
|
||||
else
|
||||
base[name] = nested
|
||||
end
|
||||
|
@ -176,6 +165,8 @@ module CanCan
|
|||
end
|
||||
end
|
||||
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::Base.class_eval do
|
||||
include CanCan::ModelAdditions
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ module CanCan
|
|||
|
||||
# This module adds the accessible_by class method to a model. It is included in the model adapters.
|
||||
module ModelAdditions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Returns a scope which fetches only the records that the passed ability
|
||||
# can perform a given action on. The action defaults to :index. This
|
||||
|
@ -23,9 +25,5 @@ module CanCan
|
|||
ability.model_adapter(self, action).database_records
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,37 +4,38 @@ module CanCan
|
|||
# helpful methods to determine permission checking and conditions hash generation.
|
||||
class Rule # :nodoc:
|
||||
attr_reader :base_behavior, :subjects, :actions, :conditions
|
||||
attr_writer :expanded_actions
|
||||
attr_writer :expanded_actions, :expanded_subjects
|
||||
|
||||
# The first argument when initializing is the base_behavior which is a true/false
|
||||
# value. True for "can" and false for "cannot". The next two arguments are the action
|
||||
# and subject respectively (such as :read, @project). The third argument is a hash
|
||||
# of conditions and the last one is the block passed to the "can" call.
|
||||
def initialize(base_behavior, action, subject, conditions, block)
|
||||
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
|
||||
def initialize(base_behavior, action = nil, subject = nil, *extra_args, &block)
|
||||
@match_all = action.nil? && subject.nil?
|
||||
@base_behavior = base_behavior
|
||||
@actions = [action].flatten
|
||||
@subjects = [subject].flatten
|
||||
@conditions = conditions || {}
|
||||
@attributes = [extra_args.shift].flatten if extra_args.first.kind_of?(Symbol) || extra_args.first.kind_of?(Array) && extra_args.first.first.kind_of?(Symbol)
|
||||
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if extra_args.first.kind_of?(Hash) && !block.nil?
|
||||
@conditions = extra_args.first || {}
|
||||
@block = block
|
||||
end
|
||||
|
||||
# Matches both the subject and action, not necessarily the conditions
|
||||
def relevant?(action, subject)
|
||||
# Matches the subject, action, and given attribute. Conditions are not checked here.
|
||||
def relevant?(action, subject, attribute)
|
||||
subject = subject.values.first if subject.class == Hash
|
||||
@match_all || (matches_action?(action) && matches_subject?(subject))
|
||||
@match_all || (matches_action?(action) && matches_subject?(subject) && matches_attribute?(attribute))
|
||||
end
|
||||
|
||||
# Matches the block or conditions hash
|
||||
def matches_conditions?(action, subject, extra_args)
|
||||
def matches_conditions?(action, subject, attribute)
|
||||
if @match_all
|
||||
call_block_with_all(action, subject, extra_args)
|
||||
elsif @block && !subject_class?(subject)
|
||||
@block.call(subject, *extra_args)
|
||||
call_block_with_all(action, subject, attribute)
|
||||
elsif @block && subject_object?(subject)
|
||||
@block.arity == 1 ? @block.call(subject) : @block.call(subject, attribute)
|
||||
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
||||
nested_subject_matches_conditions?(subject)
|
||||
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
||||
elsif @conditions.kind_of?(Hash) && subject_object?(subject)
|
||||
matches_conditions_hash?(subject)
|
||||
else
|
||||
# Don't stop at "cannot" definitions when there are conditions.
|
||||
|
@ -43,20 +44,27 @@ module CanCan
|
|||
end
|
||||
|
||||
def only_block?
|
||||
conditions_empty? && !@block.nil?
|
||||
!conditions? && !@block.nil?
|
||||
end
|
||||
|
||||
def only_raw_sql?
|
||||
@block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
|
||||
@block.nil? && conditions? && !@conditions.kind_of?(Hash)
|
||||
end
|
||||
|
||||
def conditions_empty?
|
||||
@conditions == {} || @conditions.nil?
|
||||
def attributes?
|
||||
@attributes.present?
|
||||
end
|
||||
|
||||
def conditions?
|
||||
@conditions.present?
|
||||
end
|
||||
|
||||
def instance_conditions?
|
||||
@block || conditions?
|
||||
end
|
||||
|
||||
def unmergeable?
|
||||
@conditions.respond_to?(:keys) && @conditions.present? &&
|
||||
(!@conditions.keys.first.kind_of? Symbol)
|
||||
@conditions.respond_to?(:keys) && (! @conditions.keys.first.kind_of? Symbol)
|
||||
end
|
||||
|
||||
def associations_hash(conditions = @conditions)
|
||||
|
@ -75,23 +83,42 @@ module CanCan
|
|||
attributes
|
||||
end
|
||||
|
||||
def specificity
|
||||
specificity = 1
|
||||
specificity += 1 if attributes? || conditions?
|
||||
specificity += 2 unless base_behavior
|
||||
specificity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subject_class?(subject)
|
||||
klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
||||
klass == Class || klass == Module
|
||||
def subject_object?(subject)
|
||||
# klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
||||
# klass == Class || klass == Module
|
||||
!subject.kind_of?(Symbol) && !subject.kind_of?(String)
|
||||
end
|
||||
|
||||
def matches_action?(action)
|
||||
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
|
||||
@expanded_actions.include?(:access) || @expanded_actions.include?(action.to_sym)
|
||||
end
|
||||
|
||||
def matches_subject?(subject)
|
||||
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
|
||||
subject = subject_name(subject) if subject_object? subject
|
||||
@expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
|
||||
end
|
||||
|
||||
def matches_attribute?(attribute)
|
||||
# don't consider attributes in a cannot clause when not matching - this can probably be refactored
|
||||
if !@base_behavior && @attributes && attribute.nil?
|
||||
false
|
||||
else
|
||||
@attributes.nil? || attribute.nil? || @attributes.include?(attribute.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO deperecate this
|
||||
def matches_subject_class?(subject)
|
||||
@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)) }
|
||||
@expanded_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)) }
|
||||
end
|
||||
|
||||
# Checks if the given subject matches the given conditions hash.
|
||||
|
@ -114,9 +141,9 @@ module CanCan
|
|||
if attribute.kind_of? Array
|
||||
attribute.any? { |element| matches_conditions_hash? element, value }
|
||||
else
|
||||
!attribute.nil? && matches_conditions_hash?(attribute, value)
|
||||
attribute && matches_conditions_hash?(attribute, value)
|
||||
end
|
||||
elsif !value.is_a?(String) && value.kind_of?(Enumerable)
|
||||
elsif value.kind_of?(Enumerable)
|
||||
value.include? attribute
|
||||
else
|
||||
attribute == value
|
||||
|
@ -132,16 +159,20 @@ module CanCan
|
|||
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
||||
end
|
||||
|
||||
def call_block_with_all(action, subject, extra_args)
|
||||
if subject.class == Class
|
||||
@block.call(action, subject, nil, *extra_args)
|
||||
def call_block_with_all(action, subject, attribute)
|
||||
if subject_object? subject
|
||||
@block.call(action, subject_name(subject), subject, attribute)
|
||||
else
|
||||
@block.call(action, subject.class, subject, *extra_args)
|
||||
@block.call(action, subject, nil, attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def subject_name(subject)
|
||||
subject.class.to_s.underscore.pluralize.to_sym
|
||||
end
|
||||
|
||||
def model_adapter(subject)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Description:
|
||||
The cancan:ability generator creates an Ability class in the models
|
||||
directory. You can move this file anywhere you want as long as it
|
||||
is in the load path.
|
||||
is in the load path. A test/spec file is also generated depending
|
||||
on if a spec directory exists.
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
module Cancan
|
||||
module Generators
|
||||
class AbilityGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
source_root File.expand_path("../templates", __FILE__)
|
||||
|
||||
def generate_ability
|
||||
copy_file "ability.rb", "app/models/ability.rb"
|
||||
if File.exist?(File.join(destination_root, "spec"))
|
||||
copy_file "ability_spec.rb", "spec/models/ability_spec.rb"
|
||||
else
|
||||
copy_file "ability_test.rb", "test/unit/ability_test.rb"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,31 +2,23 @@ class Ability
|
|||
include CanCan::Ability
|
||||
|
||||
def initialize(user)
|
||||
# Define abilities for the passed in user here. For example:
|
||||
# Define abilities for the passed in (current) user. For example:
|
||||
#
|
||||
# user ||= User.new # guest user (not logged in)
|
||||
# if user.admin?
|
||||
# can :manage, :all
|
||||
# if user
|
||||
# can :access, :all
|
||||
# else
|
||||
# can :read, :all
|
||||
# can :access, :home
|
||||
# can :create, [:users, :sessions]
|
||||
# end
|
||||
#
|
||||
# The first argument to `can` is the action you are giving the user
|
||||
# permission to do.
|
||||
# If you pass :manage it will apply to every action. Other common actions
|
||||
# here are :read, :create, :update and :destroy.
|
||||
# Here if there is a user he will be able to perform any action on any controller.
|
||||
# If someone is not logged in he can only access the home, users, and sessions controllers.
|
||||
#
|
||||
# The second argument is the resource the user can perform the action on.
|
||||
# If you pass :all it will apply to every resource. Otherwise pass a Ruby
|
||||
# class of the resource.
|
||||
# The first argument to `can` is the action the user can perform. The second argument
|
||||
# is the controller name they can perform that action on. You can pass :access and :all
|
||||
# to represent any action and controller respectively. Passing an array to either of
|
||||
# these will grant permission on each item in the array.
|
||||
#
|
||||
# The third argument is an optional hash of conditions to further filter the
|
||||
# objects.
|
||||
# For example, here the user can only update published articles.
|
||||
#
|
||||
# can :update, Article, :published => true
|
||||
#
|
||||
# See the wiki for details:
|
||||
# https://github.com/ryanb/cancan/wiki/Defining-Abilities
|
||||
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
|
||||
end
|
||||
end
|
||||
|
|
16
lib/generators/cancan/ability/templates/ability_spec.rb
Normal file
16
lib/generators/cancan/ability/templates/ability_spec.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
require "spec_helper"
|
||||
require "cancan/matchers"
|
||||
|
||||
describe Ability do
|
||||
describe "as guest" do
|
||||
before(:each) do
|
||||
@ability = Ability.new(nil)
|
||||
end
|
||||
|
||||
it "can only create a user" do
|
||||
# Define what a guest can and cannot do
|
||||
# @ability.should be_able_to(:create, :users)
|
||||
# @ability.should_not be_able_to(:update, :users)
|
||||
end
|
||||
end
|
||||
end
|
10
lib/generators/cancan/ability/templates/ability_test.rb
Normal file
10
lib/generators/cancan/ability/templates/ability_test.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
require "test_helper"
|
||||
|
||||
class AbilityTest < ActiveSupport::TestCase
|
||||
def guest_can_only_create_user
|
||||
ability = Ability.new(nil)
|
||||
# Define what a guest can and cannot do
|
||||
# assert ability.can?(:create, :users)
|
||||
# assert ability.cannot?(:update, :users)
|
||||
end
|
||||
end
|
|
@ -6,358 +6,421 @@ describe CanCan::Ability do
|
|||
@ability.extend(CanCan::Ability)
|
||||
end
|
||||
|
||||
it "should be able to :read anything" do
|
||||
@ability.can :read, :all
|
||||
@ability.can?(:read, String).should be_true
|
||||
@ability.can?(:read, 123).should be_true
|
||||
|
||||
# Basic Action & Subject
|
||||
|
||||
it "allows access to only what is defined" do
|
||||
@ability.can?(:paint, :fences).should be_false
|
||||
@ability.can :paint, :fences
|
||||
@ability.can?(:paint, :fences).should be_true
|
||||
@ability.can?(:wax, :fences).should be_false
|
||||
@ability.can?(:paint, :cars).should be_false
|
||||
end
|
||||
|
||||
it "should not have permission to do something it doesn't know about" do
|
||||
@ability.can?(:foodfight, String).should be_false
|
||||
it "allows access to everything when :access, :all is used" do
|
||||
@ability.can?(:paint, :fences).should be_false
|
||||
@ability.can :access, :all
|
||||
@ability.can?(:paint, :fences).should be_true
|
||||
@ability.can?(:wax, :fences).should be_true
|
||||
@ability.can?(:paint, :cars).should be_true
|
||||
end
|
||||
|
||||
it "should pass true to `can?` when non false/nil is returned in block" do
|
||||
@ability.can :read, :all
|
||||
@ability.can :read, Symbol do |sym|
|
||||
"foo" # TODO test that sym is nil when no instance is passed
|
||||
end
|
||||
@ability.can?(:read, :some_symbol).should == true
|
||||
it "allows access to multiple actions and subjects" do
|
||||
@ability.can [:paint, :sand], [:fences, :decks]
|
||||
@ability.can?(:paint, :fences).should be_true
|
||||
@ability.can?(:sand, :fences).should be_true
|
||||
@ability.can?(:paint, :decks).should be_true
|
||||
@ability.can?(:sand, :decks).should be_true
|
||||
@ability.can?(:wax, :fences).should be_false
|
||||
@ability.can?(:paint, :cars).should be_false
|
||||
end
|
||||
|
||||
it "should pass nil to a block when no instance is passed" do
|
||||
@ability.can :read, Symbol do |sym|
|
||||
sym.should be_nil
|
||||
true
|
||||
end
|
||||
@ability.can?(:read, Symbol).should be_true
|
||||
it "allows strings instead of symbols in ability check" do
|
||||
@ability.can :paint, :fences
|
||||
@ability.can?("paint", "fences").should be_true
|
||||
end
|
||||
|
||||
it "should pass to previous rule, if block returns false or nil" do
|
||||
@ability.can :read, Symbol
|
||||
@ability.can :read, Integer do |i|
|
||||
i < 5
|
||||
end
|
||||
@ability.can :read, Integer do |i|
|
||||
i > 10
|
||||
end
|
||||
@ability.can?(:read, Symbol).should be_true
|
||||
@ability.can?(:read, 11).should be_true
|
||||
@ability.can?(:read, 1).should be_true
|
||||
@ability.can?(:read, 6).should be_false
|
||||
|
||||
# Aliases
|
||||
|
||||
it "has default index, show, new, update, delete aliases" do
|
||||
@ability.can :read, :projects
|
||||
@ability.can?(:index, :projects).should be_true
|
||||
@ability.can?(:show, :projects).should be_true
|
||||
@ability.can :create, :projects
|
||||
@ability.can?(:new, :projects).should be_true
|
||||
@ability.can :update, :projects
|
||||
@ability.can?(:edit, :projects).should be_true
|
||||
@ability.can :destroy, :projects
|
||||
@ability.can?(:delete, :projects).should be_true
|
||||
end
|
||||
|
||||
it "should not pass class with object if :all objects are accepted" do
|
||||
@ability.can :preview, :all do |object|
|
||||
object.should == 123
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:preview, 123)
|
||||
@block_called.should be_true
|
||||
end
|
||||
|
||||
it "should not call block when only class is passed, only return true" do
|
||||
@block_called = false
|
||||
@ability.can :preview, :all do |object|
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:preview, Hash).should be_true
|
||||
@block_called.should be_false
|
||||
end
|
||||
|
||||
it "should pass only object for global manage actions" do
|
||||
@ability.can :manage, String do |object|
|
||||
object.should == "foo"
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:stuff, "foo").should
|
||||
@block_called.should be_true
|
||||
end
|
||||
|
||||
it "should alias update or destroy actions to modify action" do
|
||||
it "follows deep action aliases" do
|
||||
@ability.alias_action :update, :destroy, :to => :modify
|
||||
@ability.can :modify, :all
|
||||
@ability.can?(:update, 123).should be_true
|
||||
@ability.can?(:destroy, 123).should be_true
|
||||
@ability.can :modify, :projects
|
||||
@ability.can?(:update, :projects).should be_true
|
||||
@ability.can?(:destroy, :projects).should be_true
|
||||
@ability.can?(:edit, :projects).should be_true
|
||||
end
|
||||
|
||||
it "should allow deeply nested aliased actions" do
|
||||
@ability.alias_action :increment, :to => :sort
|
||||
@ability.alias_action :sort, :to => :modify
|
||||
@ability.can :modify, :all
|
||||
@ability.can?(:increment, 123).should be_true
|
||||
end
|
||||
|
||||
it "should raise an Error if alias target is an exist action" do
|
||||
lambda{ @ability.alias_action :show, :to => :show }.should raise_error(CanCan::Error, "You can't specify target (show) as alias because it is real action name")
|
||||
end
|
||||
|
||||
it "should always call block with arguments when passing no arguments to can" do
|
||||
@ability.can do |action, object_class, object|
|
||||
action.should == :foo
|
||||
object_class.should == 123.class
|
||||
object.should == 123
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:foo, 123)
|
||||
@block_called.should be_true
|
||||
end
|
||||
|
||||
it "should pass nil to object when comparing class with can check" do
|
||||
@ability.can do |action, object_class, object|
|
||||
action.should == :foo
|
||||
object_class.should == Hash
|
||||
object.should be_nil
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:foo, Hash)
|
||||
@block_called.should be_true
|
||||
end
|
||||
|
||||
it "should automatically alias index and show into read calls" do
|
||||
@ability.can :read, :all
|
||||
@ability.can?(:index, 123).should be_true
|
||||
@ability.can?(:show, 123).should be_true
|
||||
end
|
||||
|
||||
it "should automatically alias new and edit into create and update respectively" do
|
||||
@ability.can :create, :all
|
||||
@ability.can :update, :all
|
||||
@ability.can?(:new, 123).should be_true
|
||||
@ability.can?(:edit, 123).should be_true
|
||||
end
|
||||
|
||||
it "should not respond to prepare (now using initialize)" do
|
||||
@ability.should_not respond_to(:prepare)
|
||||
end
|
||||
|
||||
it "should offer cannot? method which is simply invert of can?" do
|
||||
@ability.cannot?(:tie, String).should be_true
|
||||
end
|
||||
|
||||
it "should be able to specify multiple actions and match any" do
|
||||
@ability.can [:read, :update], :all
|
||||
@ability.can?(:read, 123).should be_true
|
||||
@ability.can?(:update, 123).should be_true
|
||||
@ability.can?(:count, 123).should be_false
|
||||
end
|
||||
|
||||
it "should be able to specify multiple classes and match any" do
|
||||
@ability.can :update, [String, Range]
|
||||
@ability.can?(:update, "foo").should be_true
|
||||
@ability.can?(:update, 1..3).should be_true
|
||||
@ability.can?(:update, 123).should be_false
|
||||
end
|
||||
|
||||
it "should support custom objects in the rule" do
|
||||
@ability.can :read, :stats
|
||||
@ability.can?(:read, :stats).should be_true
|
||||
@ability.can?(:update, :stats).should be_false
|
||||
@ability.can?(:read, :nonstats).should be_false
|
||||
end
|
||||
|
||||
it "should check ancestors of class" do
|
||||
@ability.can :read, Numeric
|
||||
@ability.can?(:read, Integer).should be_true
|
||||
@ability.can?(:read, 1.23).should be_true
|
||||
@ability.can?(:read, "foo").should be_false
|
||||
end
|
||||
|
||||
it "should support 'cannot' method to define what user cannot do" do
|
||||
@ability.can :read, :all
|
||||
@ability.cannot :read, Integer
|
||||
@ability.can?(:read, "foo").should be_true
|
||||
@ability.can?(:read, 123).should be_false
|
||||
end
|
||||
|
||||
it "should pass to previous rule, if block returns false or nil" do
|
||||
@ability.can :read, :all
|
||||
@ability.cannot :read, Integer do |int|
|
||||
int > 10 ? nil : ( int > 5 )
|
||||
end
|
||||
@ability.can?(:read, "foo").should be_true
|
||||
@ability.can?(:read, 3).should be_true
|
||||
@ability.can?(:read, 8).should be_false
|
||||
@ability.can?(:read, 123).should be_true
|
||||
end
|
||||
|
||||
it "should always return `false` for single cannot definition" do
|
||||
@ability.cannot :read, Integer do |int|
|
||||
int > 10 ? nil : ( int > 5 )
|
||||
end
|
||||
@ability.can?(:read, "foo").should be_false
|
||||
@ability.can?(:read, 3).should be_false
|
||||
@ability.can?(:read, 8).should be_false
|
||||
@ability.can?(:read, 123).should be_false
|
||||
end
|
||||
|
||||
it "should pass to previous cannot definition, if block returns false or nil" do
|
||||
@ability.cannot :read, :all
|
||||
@ability.can :read, Integer do |int|
|
||||
int > 10 ? nil : ( int > 5 )
|
||||
end
|
||||
@ability.can?(:read, "foo").should be_false
|
||||
@ability.can?(:read, 3).should be_false
|
||||
@ability.can?(:read, 10).should be_true
|
||||
@ability.can?(:read, 123).should be_false
|
||||
end
|
||||
|
||||
it "should append aliased actions" do
|
||||
it "adds up action aliases" do
|
||||
@ability.alias_action :update, :to => :modify
|
||||
@ability.alias_action :destroy, :to => :modify
|
||||
@ability.aliased_actions[:modify].should == [:update, :destroy]
|
||||
@ability.can :modify, :projects
|
||||
@ability.can?(:update, :projects).should be_true
|
||||
@ability.can?(:destroy, :projects).should be_true
|
||||
end
|
||||
|
||||
it "should clear aliased actions" do
|
||||
@ability.alias_action :update, :to => :modify
|
||||
@ability.clear_aliased_actions
|
||||
@ability.aliased_actions[:modify].should be_nil
|
||||
it "follows deep subject aliases" do
|
||||
@ability.alias_subject :mammals, :to => :animals
|
||||
@ability.alias_subject :cats, :to => :mammals
|
||||
@ability.can :pet, :animals
|
||||
@ability.can?(:pet, :mammals).should be_true
|
||||
end
|
||||
|
||||
it "should pass additional arguments to block from can?" do
|
||||
@ability.can :read, Integer do |int, x|
|
||||
int > x
|
||||
end
|
||||
@ability.can?(:read, 2, 1).should be_true
|
||||
@ability.can?(:read, 2, 3).should be_false
|
||||
it "clears current and default aliases" do
|
||||
@ability.alias_action :update, :destroy, :to => :modify
|
||||
@ability.clear_aliases
|
||||
@ability.can :modify, :projects
|
||||
@ability.can?(:update, :projects).should be_false
|
||||
@ability.can :read, :projects
|
||||
@ability.can?(:show, :projects).should be_false
|
||||
end
|
||||
|
||||
it "should use conditions as third parameter and determine abilities from it" do
|
||||
@ability.can :read, Range, :begin => 1, :end => 3
|
||||
|
||||
# Hash Conditions
|
||||
|
||||
it "maps object to pluralized subject name" do
|
||||
@ability.can :read, :ranges
|
||||
@ability.can?(:read, :ranges).should be_true
|
||||
@ability.can?(:read, 1..3).should be_true
|
||||
@ability.can?(:read, 1..4).should be_false
|
||||
@ability.can?(:read, Range).should be_true
|
||||
@ability.can?(:read, 123).should be_false
|
||||
end
|
||||
|
||||
it "should allow an array of options in conditions hash" do
|
||||
@ability.can :read, Range, :begin => [1, 3, 5]
|
||||
it "checks conditions hash on instances only" do
|
||||
@ability.can :read, :ranges, :begin => 1
|
||||
@ability.can?(:read, :ranges).should be_true
|
||||
@ability.can?(:read, 1..3).should be_true
|
||||
@ability.can?(:read, 2..4).should be_false
|
||||
end
|
||||
|
||||
it "checks conditions on both rules and matches either one" do
|
||||
@ability.can :read, :ranges, :begin => 1
|
||||
@ability.can :read, :ranges, :begin => 2
|
||||
@ability.can?(:read, 1..3).should be_true
|
||||
@ability.can?(:read, 2..4).should be_true
|
||||
@ability.can?(:read, 3..5).should be_false
|
||||
end
|
||||
|
||||
it "checks an array of options in conditions hash" do
|
||||
@ability.can :read, :ranges, :begin => [1, 3, 5]
|
||||
@ability.can?(:read, 1..3).should be_true
|
||||
@ability.can?(:read, 2..4).should be_false
|
||||
@ability.can?(:read, 3..5).should be_true
|
||||
end
|
||||
|
||||
it "should allow a range of options in conditions hash" do
|
||||
@ability.can :read, Range, :begin => 1..3
|
||||
it "checks a range of options in conditions hash" do
|
||||
@ability.can :read, :ranges, :begin => 1..3
|
||||
@ability.can?(:read, 1..10).should be_true
|
||||
@ability.can?(:read, 3..30).should be_true
|
||||
@ability.can?(:read, 4..40).should be_false
|
||||
end
|
||||
|
||||
it "should allow nested hashes in conditions hash" do
|
||||
@ability.can :read, Range, :begin => { :to_i => 5 }
|
||||
it "checks nested conditions hash" do
|
||||
@ability.can :read, :ranges, :begin => { :to_i => 5 }
|
||||
@ability.can?(:read, 5..7).should be_true
|
||||
@ability.can?(:read, 6..8).should be_false
|
||||
end
|
||||
|
||||
it "should match any element passed in to nesting if it's an array (for has_many associations)" do
|
||||
@ability.can :read, Range, :to_a => { :to_i => 3 }
|
||||
it "matches any element passed in to nesting if it's an array (for has_many associations)" do
|
||||
@ability.can :read, :ranges, :to_a => { :to_i => 3 }
|
||||
@ability.can?(:read, 1..5).should be_true
|
||||
@ability.can?(:read, 4..6).should be_false
|
||||
end
|
||||
|
||||
it "takes presedence over rule defined without a condition" do
|
||||
@ability.can :read, :ranges
|
||||
@ability.can :read, :ranges, :begin => 1
|
||||
@ability.can?(:read, 1..5).should be_true
|
||||
@ability.can?(:read, 4..6).should be_false
|
||||
end
|
||||
|
||||
|
||||
# Block Conditions
|
||||
|
||||
it "executes block passing object only when instance is used" do
|
||||
@ability.can :read, :ranges do |range|
|
||||
range.begin == 5
|
||||
end
|
||||
@ability.can?(:read, :ranges).should be_true
|
||||
@ability.can?(:read, 5..7).should be_true
|
||||
@ability.can?(:read, 6..8).should be_false
|
||||
end
|
||||
|
||||
it "returns true when other object is returned in block" do
|
||||
@ability.can :read, :ranges do |range|
|
||||
"foo"
|
||||
end
|
||||
@ability.can?(:read, 5..7).should be_true
|
||||
end
|
||||
|
||||
it "passes to previous rule when block returns false" do
|
||||
@ability.can :read, :fixnums do |i|
|
||||
i < 5
|
||||
end
|
||||
@ability.can :read, :fixnums do |i|
|
||||
i > 10
|
||||
end
|
||||
@ability.can?(:read, 11).should be_true
|
||||
@ability.can?(:read, 1).should be_true
|
||||
@ability.can?(:read, 6).should be_false
|
||||
end
|
||||
|
||||
it "calls block passing arguments when no arguments are given to can" do
|
||||
@ability.can do |action, subject, object|
|
||||
action.should == :read
|
||||
subject.should == :ranges
|
||||
object.should == (2..4)
|
||||
@block_called = true
|
||||
end
|
||||
@ability.can?(:read, 2..4)
|
||||
@block_called.should be_true
|
||||
end
|
||||
|
||||
it "raises an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
||||
lambda {
|
||||
@ability.can :read, :ranges, :published => true do
|
||||
false
|
||||
end
|
||||
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read ranges ability. Use either one.")
|
||||
end
|
||||
|
||||
it "does not raise an error when attempting to use a block with an array of SQL conditions" do
|
||||
lambda {
|
||||
@ability.can :read, :ranges, ["published = ?", true] do
|
||||
false
|
||||
end
|
||||
}.should_not raise_error(CanCan::Error)
|
||||
end
|
||||
|
||||
|
||||
# Attributes
|
||||
|
||||
it "allows permission on attributes" do
|
||||
@ability.can :update, :users, :name
|
||||
@ability.can :update, :users, [:email, :age]
|
||||
@ability.can?(:update, :users, :name).should be_true
|
||||
@ability.can?(:update, :users, :email).should be_true
|
||||
@ability.can?(:update, :users, :password).should be_false
|
||||
end
|
||||
|
||||
it "allows permission on all attributes when none are given" do
|
||||
@ability.can :update, :users
|
||||
@ability.can?(:update, :users, :password).should be_true
|
||||
end
|
||||
|
||||
it "allows strings when chekcing attributes" do
|
||||
@ability.can :update, :users, :name
|
||||
@ability.can?(:update, :users, "name").should be_true
|
||||
end
|
||||
|
||||
it "combines attribute check with conditions hash" do
|
||||
@ability.can :update, :ranges, :begin => 1
|
||||
@ability.can :update, :ranges, :name, :begin => 2
|
||||
@ability.can?(:update, 1..3, :foobar).should be_true
|
||||
@ability.can?(:update, 2..4, :foobar).should be_false
|
||||
@ability.can?(:update, 2..4, :name).should be_true
|
||||
@ability.can?(:update, 3..5, :name).should be_false
|
||||
end
|
||||
|
||||
it "passes attribute to block and nil if no attribute checked" do
|
||||
@ability.can :update, :ranges do |range, attribute|
|
||||
attribute == :name
|
||||
end
|
||||
@ability.can?(:update, 1..3, :name).should be_true
|
||||
@ability.can?(:update, 2..4).should be_false
|
||||
end
|
||||
|
||||
it "passes attribute to block for global can definition" do
|
||||
@ability.can do |action, subject, object, attribute|
|
||||
attribute == :name
|
||||
end
|
||||
@ability.can?(:update, 1..3, :name).should be_true
|
||||
@ability.can?(:update, 2..4).should be_false
|
||||
end
|
||||
|
||||
|
||||
# Checking if Fully Authorized
|
||||
|
||||
it "is not fully authorized when no authorize! call is made" do
|
||||
@ability.can :update, :ranges, :begin => 1
|
||||
@ability.can?(:update, :ranges).should be_true
|
||||
@ability.should_not be_fully_authorized(:update, :ranges)
|
||||
end
|
||||
|
||||
it "is fully authorized when calling authorize! with a matching action and subject" do
|
||||
@ability.can :update, :ranges
|
||||
@ability.authorize! :update, :ranges
|
||||
@ability.should be_fully_authorized(:update, :ranges)
|
||||
@ability.should_not be_fully_authorized(:create, :ranges)
|
||||
end
|
||||
|
||||
it "is fully authorized when marking action and subject as such" do
|
||||
@ability.fully_authorized! :update, :ranges
|
||||
@ability.should be_fully_authorized(:update, :ranges)
|
||||
end
|
||||
|
||||
it "is not fully authorized when a conditions hash exists but no instance is used" do
|
||||
@ability.can :update, :ranges, :begin => 1
|
||||
@ability.authorize! :update, :ranges
|
||||
@ability.should_not be_fully_authorized(:update, :ranges)
|
||||
@ability.authorize! "update", "ranges"
|
||||
@ability.should_not be_fully_authorized(:update, :ranges)
|
||||
@ability.authorize! :update, 1..3
|
||||
@ability.should be_fully_authorized(:update, :ranges)
|
||||
end
|
||||
|
||||
it "is not fully authorized when a block exists but no instance is used" do
|
||||
@ability.can :update, :ranges do |range|
|
||||
range.begin == 1
|
||||
end
|
||||
@ability.authorize! :update, :ranges
|
||||
@ability.should_not be_fully_authorized(:update, :ranges)
|
||||
@ability.authorize! :update, 1..3
|
||||
@ability.should be_fully_authorized(:update, :ranges)
|
||||
end
|
||||
|
||||
it "should accept a set as a condition value" do
|
||||
mock(object_with_foo_2 = Object.new).foo { 2 }
|
||||
mock(object_with_foo_3 = Object.new).foo { 3 }
|
||||
@ability.can :read, Object, :foo => [1, 2, 5].to_set
|
||||
object_with_foo_2 = Object.new
|
||||
object_with_foo_2.should_receive(:foo) { 2 }
|
||||
object_with_foo_3 = Object.new
|
||||
object_with_foo_3.should_receive(:foo) { 3 }
|
||||
@ability.can :read, :objects, :foo => [1, 2, 5].to_set
|
||||
@ability.can?(:read, object_with_foo_2).should be_true
|
||||
@ability.can?(:read, object_with_foo_3).should be_false
|
||||
end
|
||||
|
||||
it "should not match subjects return nil for methods that must match nested a nested conditions hash" do
|
||||
mock(object_with_foo = Object.new).foo { :bar }
|
||||
@ability.can :read, Array, :first => { :foo => :bar }
|
||||
it "does not match subjects return nil for methods that must match nested a nested conditions hash" do
|
||||
object_with_foo = Object.new
|
||||
object_with_foo.should_receive(:foo) { :bar }
|
||||
@ability.can :read, :arrays, :first => { :foo => :bar }
|
||||
@ability.can?(:read, [object_with_foo]).should be_true
|
||||
@ability.can?(:read, []).should be_false
|
||||
end
|
||||
|
||||
it "should match strings but not substrings specified in a conditions hash" do
|
||||
@ability.can :read, String, :presence => "declassified"
|
||||
@ability.can?(:read, "declassified").should be_true
|
||||
@ability.can?(:read, "classified").should be_false
|
||||
it "is not fully authorized when attributes are required but not checked in update/create actions" do
|
||||
@ability.can :access, :users, :name
|
||||
@ability.authorize! :update, :users
|
||||
@ability.should_not be_fully_authorized(:update, :users)
|
||||
@ability.authorize! :create, :users
|
||||
@ability.should_not be_fully_authorized(:create, :users)
|
||||
@ability.authorize! :create, :users, :name
|
||||
@ability.should be_fully_authorized(:create, :users)
|
||||
@ability.authorize! :destroy, :users
|
||||
@ability.should be_fully_authorized(:destroy, :users)
|
||||
end
|
||||
|
||||
it "should not stop at cannot definition when comparing class" do
|
||||
@ability.can :read, Range
|
||||
@ability.cannot :read, Range, :begin => 1
|
||||
@ability.can?(:read, 2..5).should be_true
|
||||
@ability.can?(:read, 1..5).should be_false
|
||||
@ability.can?(:read, Range).should be_true
|
||||
it "marks as fully authorized when authorizing with strings instead of symbols" do
|
||||
@ability.fully_authorized! "update", "ranges"
|
||||
@ability.should be_fully_authorized(:update, :ranges)
|
||||
@ability.should be_fully_authorized("update", "ranges")
|
||||
@ability.can :update, :users
|
||||
@ability.authorize! "update", "users"
|
||||
@ability.should be_fully_authorized(:update, :users)
|
||||
end
|
||||
|
||||
it "should stop at cannot definition when no hash is present" do
|
||||
|
||||
# Cannot
|
||||
|
||||
it "offers cannot? method which inverts can?" do
|
||||
@ability.cannot?(:wax, :cars).should be_true
|
||||
end
|
||||
|
||||
it "supports 'cannot' method to define what user cannot do" do
|
||||
@ability.can :read, :all
|
||||
@ability.cannot :read, Range
|
||||
@ability.can?(:read, 1..5).should be_false
|
||||
@ability.can?(:read, Range).should be_false
|
||||
@ability.cannot :read, :ranges
|
||||
@ability.can?(:read, :books).should be_true
|
||||
@ability.can?(:read, 1..3).should be_false
|
||||
@ability.can?(:read, :ranges).should be_false
|
||||
end
|
||||
|
||||
it "should allow to check ability for Module" do
|
||||
module B; end
|
||||
class A; include B; end
|
||||
@ability.can :read, B
|
||||
@ability.can?(:read, A).should be_true
|
||||
@ability.can?(:read, A.new).should be_true
|
||||
it "passes to previous rule if cannot check returns false" do
|
||||
@ability.can :read, :all
|
||||
@ability.cannot :read, :ranges, :begin => 3
|
||||
@ability.cannot :read, :ranges do |range|
|
||||
range.begin == 5
|
||||
end
|
||||
@ability.can?(:read, :books).should be_true
|
||||
@ability.can?(:read, 2..4).should be_true
|
||||
@ability.can?(:read, 3..7).should be_false
|
||||
@ability.can?(:read, 5..9).should be_false
|
||||
end
|
||||
|
||||
it "should pass nil to a block for ability on Module when no instance is passed" do
|
||||
module B; end
|
||||
class A; include B; end
|
||||
@ability.can :read, B do |sym|
|
||||
sym.should be_nil
|
||||
true
|
||||
end
|
||||
@ability.can?(:read, B).should be_true
|
||||
@ability.can?(:read, A).should be_true
|
||||
it "rejects permission only to a given attribute" do
|
||||
@ability.can :update, :books
|
||||
@ability.cannot :update, :books, :author
|
||||
@ability.can?(:update, :books).should be_true
|
||||
@ability.can?(:update, :books, :author).should be_false
|
||||
end
|
||||
|
||||
it "passing a hash of subjects should check permissions through association" do
|
||||
@ability.can :read, Range, :string => {:length => 3}
|
||||
@ability.can?(:read, "foo" => Range).should be_true
|
||||
@ability.can?(:read, "foobar" => Range).should be_false
|
||||
@ability.can?(:read, 123 => Range).should be_true
|
||||
# Hash Association
|
||||
|
||||
it "checks permission through association when hash is passed as subject" do
|
||||
@ability.can :read, :books, :range => {:begin => 3}
|
||||
@ability.can?(:read, (1..4) => :books).should be_false
|
||||
@ability.can?(:read, (3..5) => :books).should be_true
|
||||
@ability.can?(:read, 123 => :books).should be_true
|
||||
end
|
||||
|
||||
it "passing a hash of subjects with multiple definitions should check permissions correctly" do
|
||||
@ability.can :read, Range, :string => {:length => 4}
|
||||
@ability.can [:create, :read], Range, :string => {:upcase => 'FOO'}
|
||||
@ability.can?(:read, "foo" => Range).should be_true
|
||||
@ability.can?(:read, "foobar" => Range).should be_false
|
||||
@ability.can?(:read, 1234 => Range).should be_true
|
||||
it "checks permissions on association hash with multiple rules" do
|
||||
@ability.can :read, :books, :range => {:begin => 3}
|
||||
@ability.can :read, :books, :range => {:end => 6}
|
||||
@ability.can?(:read, (1..4) => :books).should be_false
|
||||
@ability.can?(:read, (3..5) => :books).should be_true
|
||||
@ability.can?(:read, (1..6) => :books).should be_true
|
||||
@ability.can?(:read, 123 => :books).should be_true
|
||||
end
|
||||
|
||||
it "should allow to check ability on Hash-like object" do
|
||||
it "checks ability on hash subclass" do
|
||||
class Container < Hash; end
|
||||
@ability.can :read, Container
|
||||
@ability.can :read, :containers
|
||||
@ability.can?(:read, Container.new).should be_true
|
||||
end
|
||||
|
||||
it "should have initial attributes based on hash conditions of 'new' action" do
|
||||
@ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
|
||||
@ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
|
||||
@ability.can :new, Range, :baz => "baz", :range => 1..3
|
||||
@ability.cannot :new, Range, :ignore => "me"
|
||||
@ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"}
|
||||
|
||||
# Initial Attributes
|
||||
|
||||
it "has initial attributes based on hash conditions for a given action" do
|
||||
@ability.can :access, :ranges, :foo => "foo", :hash => {:skip => "hashes"}
|
||||
@ability.can :create, :ranges, :bar => 123, :array => %w[skip arrays]
|
||||
@ability.can :new, :ranges, :baz => "baz", :range => 1..3
|
||||
@ability.cannot :new, :ranges, :ignore => "me"
|
||||
@ability.attributes_for(:new, :ranges).should == {:foo => "foo", :bar => 123, :baz => "baz"}
|
||||
end
|
||||
|
||||
it "should raise access denied exception if ability us unauthorized to perform a certain action" do
|
||||
|
||||
# Unauthorized Exception
|
||||
|
||||
it "raises CanCan::Unauthorized when calling authorize! on unauthorized action" do
|
||||
begin
|
||||
@ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
|
||||
rescue CanCan::AccessDenied => e
|
||||
@ability.authorize! :read, :books, :message => "Access denied!"
|
||||
rescue CanCan::Unauthorized => e
|
||||
e.message.should == "Access denied!"
|
||||
e.action.should == :read
|
||||
e.subject.should == :foo
|
||||
e.subject.should == :books
|
||||
else
|
||||
fail "Expected CanCan::AccessDenied exception to be raised"
|
||||
fail "Expected CanCan::Unauthorized exception to be raised"
|
||||
end
|
||||
end
|
||||
|
||||
it "should not raise access denied exception if ability is authorized to perform an action and return subject" do
|
||||
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do
|
||||
@ability.can :read, :foo
|
||||
lambda {
|
||||
@ability.authorize!(:read, :foo).should == :foo
|
||||
}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should know when block is used in conditions" do
|
||||
it "knows when block is used in conditions" do
|
||||
@ability.can :read, :foo
|
||||
@ability.should_not have_block(:read, :foo)
|
||||
@ability.can :read, :foo do |foo|
|
||||
|
@ -366,85 +429,106 @@ describe CanCan::Ability do
|
|||
@ability.should have_block(:read, :foo)
|
||||
end
|
||||
|
||||
it "should know when raw sql is used in conditions" do
|
||||
it "knows when raw sql is used in conditions" do
|
||||
@ability.can :read, :foo
|
||||
@ability.should_not have_raw_sql(:read, :foo)
|
||||
@ability.can :read, :foo, 'false'
|
||||
@ability.should have_raw_sql(:read, :foo)
|
||||
end
|
||||
|
||||
it "should raise access denied exception with default message if not specified" do
|
||||
it "raises access denied exception with default message if not specified" do
|
||||
begin
|
||||
@ability.authorize! :read, :foo
|
||||
rescue CanCan::AccessDenied => e
|
||||
@ability.authorize! :read, :books
|
||||
rescue CanCan::Unauthorized => e
|
||||
e.default_message = "Access denied!"
|
||||
e.message.should == "Access denied!"
|
||||
else
|
||||
fail "Expected CanCan::AccessDenied exception to be raised"
|
||||
fail "Expected CanCan::Unauthorized exception to be raised"
|
||||
end
|
||||
end
|
||||
|
||||
it "should determine model adapter class by asking AbstractAdapter" do
|
||||
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do
|
||||
@ability.can :read, :books
|
||||
lambda {
|
||||
@ability.authorize!(:read, :books).should == :books
|
||||
}.should_not raise_error
|
||||
end
|
||||
|
||||
|
||||
# Determining Kind of Conditions
|
||||
|
||||
it "knows when a block is used for conditions" do
|
||||
@ability.can :read, :books
|
||||
@ability.should_not have_block(:read, :books)
|
||||
@ability.can :read, :books do |foo|
|
||||
false
|
||||
end
|
||||
@ability.should have_block(:read, :books)
|
||||
end
|
||||
|
||||
it "knows when raw sql is used for conditions" do
|
||||
@ability.can :read, :books
|
||||
@ability.should_not have_raw_sql(:read, :books)
|
||||
@ability.can :read, :books, 'false'
|
||||
@ability.should have_raw_sql(:read, :books)
|
||||
end
|
||||
|
||||
it "determines model adapter class by asking AbstractAdapter" do
|
||||
model_class = Object.new
|
||||
adapter_class = Object.new
|
||||
stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
|
||||
stub(adapter_class).new(model_class, []) { :adapter_instance }
|
||||
CanCan::ModelAdapters::AbstractAdapter.stub(:adapter_class).with(model_class) { adapter_class }
|
||||
adapter_class.stub(:new).with(model_class, []) { :adapter_instance }
|
||||
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
||||
end
|
||||
|
||||
it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
||||
lambda {
|
||||
@ability.can :read, Array, :published => true do
|
||||
false
|
||||
end
|
||||
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
|
||||
end
|
||||
|
||||
# Unauthorized I18n Message
|
||||
|
||||
describe "unauthorized message" do
|
||||
after(:each) do
|
||||
I18n.backend = nil
|
||||
end
|
||||
|
||||
it "should use action/subject in i18n" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
|
||||
@ability.unauthorized_message(:update, Array).should == "foo"
|
||||
@ability.unauthorized_message(:update, [1, 2, 3]).should == "foo"
|
||||
it "uses action/subject in i18n" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:update => {:ranges => "update ranges"}}
|
||||
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||
@ability.unauthorized_message(:update, 2..4).should == "update ranges"
|
||||
@ability.unauthorized_message(:update, :missing).should be_nil
|
||||
end
|
||||
|
||||
it "should use symbol as subject directly" do
|
||||
it "uses symbol as subject directly" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
|
||||
@ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
|
||||
end
|
||||
|
||||
it "should fall back to 'manage' and 'all'" do
|
||||
it "falls back to 'access' and 'all'" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {
|
||||
:manage => {:all => "manage all", :array => "manage array"},
|
||||
:update => {:all => "update all", :array => "update array"}
|
||||
:access => {:all => "access all", :ranges => "access ranges"},
|
||||
:update => {:all => "update all", :ranges => "update ranges"}
|
||||
}
|
||||
@ability.unauthorized_message(:update, Array).should == "update array"
|
||||
@ability.unauthorized_message(:update, Hash).should == "update all"
|
||||
@ability.unauthorized_message(:foo, Array).should == "manage array"
|
||||
@ability.unauthorized_message(:foo, Hash).should == "manage all"
|
||||
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||
@ability.unauthorized_message(:update, :hashes).should == "update all"
|
||||
@ability.unauthorized_message(:create, :ranges).should == "access ranges"
|
||||
@ability.unauthorized_message(:create, :hashes).should == "access all"
|
||||
end
|
||||
|
||||
it "should follow aliased actions" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
|
||||
it "follows aliases" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:modify => {:ranges => "modify ranges"}}
|
||||
@ability.alias_action :update, :to => :modify
|
||||
@ability.unauthorized_message(:update, Array).should == "modify array"
|
||||
@ability.unauthorized_message(:edit, Array).should == "modify array"
|
||||
@ability.alias_subject :areas, :to => :ranges
|
||||
@ability.unauthorized_message(:update, :areas).should == "modify ranges"
|
||||
@ability.unauthorized_message(:edit, :ranges).should == "modify ranges"
|
||||
end
|
||||
|
||||
it "should have variables for action and subject" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
||||
@ability.unauthorized_message(:update, Array).should == "update array"
|
||||
@ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
||||
@ability.unauthorized_message(:edit, 1..3).should == "edit range"
|
||||
it "has variables for action and subject" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:access => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
||||
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||
@ability.unauthorized_message(:edit, 1..3).should == "edit ranges"
|
||||
# @ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#merge" do
|
||||
it "should add the rules from the given ability" do
|
||||
it "merges the rules from another ability" do
|
||||
@ability.can :use, :tools
|
||||
another_ability = Object.new
|
||||
another_ability.extend(CanCan::Ability)
|
||||
|
@ -455,4 +539,3 @@ describe CanCan::Ability do
|
|||
@ability.send(:rules).size.should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,136 +2,117 @@ require "spec_helper"
|
|||
|
||||
describe CanCan::ControllerAdditions do
|
||||
before(:each) do
|
||||
@params = HashWithIndifferentAccess.new
|
||||
@controller_class = Class.new
|
||||
@controller = @controller_class.new
|
||||
stub(@controller).params { {} }
|
||||
stub(@controller).current_user { :current_user }
|
||||
mock(@controller_class).helper_method(:can?, :cannot?, :current_ability)
|
||||
@controller.stub(:params) { @params }
|
||||
@controller.stub(:current_user) { :current_user }
|
||||
@controller_class.should_receive(:helper_method).with(:can?, :cannot?, :current_ability)
|
||||
@controller_class.send(:include, CanCan::ControllerAdditions)
|
||||
end
|
||||
|
||||
it "should raise ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do
|
||||
lambda { @controller.unauthorized! }.should raise_error(CanCan::ImplementationRemoved)
|
||||
it "raises ImplementationRemoved when attempting to call load/authorize/skip/check calls on a controller" do
|
||||
lambda { @controller_class.load_resource }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.authorize_resource }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.skip_load_resource }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.skip_authorize_resource }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.check_authorization }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.skip_authorization_check }.should raise_error(CanCan::ImplementationRemoved)
|
||||
lambda { @controller_class.cancan_skipper }.should raise_error(CanCan::ImplementationRemoved)
|
||||
end
|
||||
|
||||
it "authorize! should assign @_authorized instance variable and pass args to current ability" do
|
||||
mock(@controller.current_ability).authorize!(:foo, :bar)
|
||||
it "authorize! should pass args to current ability" do
|
||||
@controller.current_ability.should_receive(:authorize!).with(:foo, :bar)
|
||||
@controller.authorize!(:foo, :bar)
|
||||
@controller.instance_variable_get(:@_authorized).should be_true
|
||||
end
|
||||
|
||||
it "should have a current_ability method which generates an ability for the current user" do
|
||||
@controller.current_ability.should be_kind_of(Ability)
|
||||
end
|
||||
|
||||
it "should provide a can? and cannot? methods which go through the current ability" do
|
||||
it "provides a can? and cannot? methods which go through the current ability" do
|
||||
@controller.current_ability.should be_kind_of(Ability)
|
||||
@controller.can?(:foo, :bar).should be_false
|
||||
@controller.cannot?(:foo, :bar).should be_true
|
||||
end
|
||||
|
||||
it "load_and_authorize_resource should setup a before filter which passes call to ControllerResource" do
|
||||
stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.load_and_authorize_resource
|
||||
mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
|
||||
it "load_and_authorize_resource adds a before filter which passes call to ControllerResource" do
|
||||
controller_resource = double("controller_resource")
|
||||
controller_resource.should_receive(:process)
|
||||
CanCan::ControllerResource.stub(:new).with(@controller, nil, :load => true, :authorize => true, :foo => :bar) { controller_resource }
|
||||
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
|
||||
@controller_class.load_and_authorize_resource :foo => :bar
|
||||
end
|
||||
|
||||
it "load_and_authorize_resource should properly pass first argument as the resource name" do
|
||||
stub(CanCan::ControllerResource).new(@controller, :project, :foo => :bar).mock!.load_and_authorize_resource
|
||||
mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
|
||||
it "load_and_authorize_resource passes first argument as the resource name" do
|
||||
controller_resource = double("controller_resource")
|
||||
controller_resource.should_receive(:process)
|
||||
CanCan::ControllerResource.stub(:new).with(@controller, :project, :load => true, :authorize => true, :foo => :bar) { controller_resource }
|
||||
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
|
||||
@controller_class.load_and_authorize_resource :project, :foo => :bar
|
||||
end
|
||||
|
||||
it "load_and_authorize_resource with :prepend should prepend the before filter" do
|
||||
mock(@controller_class).prepend_before_filter({})
|
||||
it "load_and_authorize_resource passes :only, :except, :if, :unless options to before filter" do
|
||||
controller_resource = double("controller_resource")
|
||||
controller_resource.should_receive(:process)
|
||||
CanCan::ControllerResource.stub(:new).with(@controller, nil, :load => true, :authorize => true) { controller_resource }
|
||||
@controller_class.should_receive(:before_filter).with(:only => 1, :except => 2, :if => 3, :unless => 4).and_yield(@controller)
|
||||
@controller_class.load_and_authorize_resource :only => 1, :except => 2, :if => 3, :unless => 4
|
||||
end
|
||||
|
||||
it "load_and_authorize_resource with :prepend prepends the before filter" do
|
||||
@controller_class.should_receive(:prepend_before_filter).with({})
|
||||
@controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
|
||||
end
|
||||
|
||||
it "authorize_resource should setup a before filter which passes call to ControllerResource" do
|
||||
stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
|
||||
mock(@controller_class).before_filter(:except => :show, :if => true) { |options, block| block.call(@controller) }
|
||||
@controller_class.authorize_resource :foo => :bar, :except => :show, :if => true
|
||||
end
|
||||
|
||||
it "load_resource should setup a before filter which passes call to ControllerResource" do
|
||||
stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.load_resource
|
||||
mock(@controller_class).before_filter(:only => [:show, :index], :unless => false) { |options, block| block.call(@controller) }
|
||||
@controller_class.load_resource :foo => :bar, :only => [:show, :index], :unless => false
|
||||
end
|
||||
|
||||
it "skip_authorization_check should set up a before filter which sets @_authorized to true" do
|
||||
mock(@controller_class).before_filter(:filter_options) { |options, block| block.call(@controller) }
|
||||
@controller_class.skip_authorization_check(:filter_options)
|
||||
@controller.instance_variable_get(:@_authorized).should be_true
|
||||
end
|
||||
|
||||
it "check_authorization should trigger AuthorizationNotPerformed in after filter" do
|
||||
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:only => [:test])
|
||||
}.should raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "check_authorization should not trigger AuthorizationNotPerformed when :if is false" do
|
||||
stub(@controller).check_auth? { false }
|
||||
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:if => :check_auth?)
|
||||
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "check_authorization should not trigger AuthorizationNotPerformed when :unless is true" do
|
||||
stub(@controller).engine_controller? { true }
|
||||
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:unless => :engine_controller?)
|
||||
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "check_authorization should not raise error when @_authorized is set" do
|
||||
@controller.instance_variable_set(:@_authorized, true)
|
||||
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:only => [:test])
|
||||
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "cancan_resource_class should be ControllerResource by default" do
|
||||
@controller.class.cancan_resource_class.should == CanCan::ControllerResource
|
||||
end
|
||||
|
||||
it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do
|
||||
stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
|
||||
@controller.class.stub(:ancestors) { ["InheritedResources::Actions"] }
|
||||
@controller.class.cancan_resource_class.should == CanCan::InheritedResource
|
||||
end
|
||||
|
||||
it "cancan_skipper should be an empty hash with :authorize and :load options and remember changes" do
|
||||
@controller_class.cancan_skipper.should == {:authorize => {}, :load => {}}
|
||||
@controller_class.cancan_skipper[:load] = true
|
||||
@controller_class.cancan_skipper[:load].should == true
|
||||
it "enable_authorization should call authorize! with controller and action name" do
|
||||
@params.merge!(:controller => "projects", :action => "create")
|
||||
@controller.should_receive(:authorize!).with("create", "projects")
|
||||
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar).and_yield(@controller)
|
||||
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar)
|
||||
@controller_class.enable_authorization(:only => :foo, :except => :bar)
|
||||
end
|
||||
|
||||
it "skip_authorize_resource should add itself to the cancan skipper with given model name and options" do
|
||||
@controller_class.skip_authorize_resource(:project, :only => [:index, :show])
|
||||
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
||||
@controller_class.skip_authorize_resource(:only => [:index, :show])
|
||||
@controller_class.cancan_skipper[:authorize][nil].should == {:only => [:index, :show]}
|
||||
@controller_class.skip_authorize_resource(:article)
|
||||
@controller_class.cancan_skipper[:authorize][:article].should == {}
|
||||
it "enable_authorization should raise InsufficientAuthorizationCheck when not fully authoried" do
|
||||
@params.merge!(:controller => "projects", :action => "create")
|
||||
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar)
|
||||
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar).and_yield(@controller)
|
||||
lambda {
|
||||
@controller_class.enable_authorization(:only => :foo, :except => :bar)
|
||||
}.should raise_error(CanCan::InsufficientAuthorizationCheck)
|
||||
end
|
||||
|
||||
it "skip_load_resource should add itself to the cancan skipper with given model name and options" do
|
||||
@controller_class.skip_load_resource(:project, :only => [:index, :show])
|
||||
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
||||
@controller_class.skip_load_resource(:only => [:index, :show])
|
||||
@controller_class.cancan_skipper[:load][nil].should == {:only => [:index, :show]}
|
||||
@controller_class.skip_load_resource(:article)
|
||||
@controller_class.cancan_skipper[:load][:article].should == {}
|
||||
it "enable_authorization should not call authorize! when :if is false" do
|
||||
@authorize_called = false
|
||||
@controller.stub(:authorize?) { false }
|
||||
@controller.stub(:authorize!) { @authorize_called = true }
|
||||
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
|
||||
@controller_class.should_receive(:after_filter).with({}).and_yield(@controller)
|
||||
@controller_class.enable_authorization(:if => :authorize?)
|
||||
@authorize_called.should be_false
|
||||
end
|
||||
|
||||
it "skip_load_and_authore_resource should add itself to the cancan skipper with given model name and options" do
|
||||
@controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show])
|
||||
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
||||
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
||||
it "enable_authorization should not call authorize! when :unless is true" do
|
||||
@authorize_called = false
|
||||
@controller.stub(:engine_controller?) { true }
|
||||
@controller.stub(:authorize!) { @authorize_called = true }
|
||||
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
|
||||
@controller_class.should_receive(:after_filter).with({}).and_yield(@controller)
|
||||
@controller_class.enable_authorization(:unless => :engine_controller?)
|
||||
@authorize_called.should be_false
|
||||
end
|
||||
|
||||
it "enable_authorization should pass block to rescue_from CanCan::Unauthorized call" do
|
||||
@block_called = false
|
||||
@controller_class.should_receive(:before_filter).with({})
|
||||
@controller_class.should_receive(:after_filter).with({})
|
||||
@controller_class.should_receive(:rescue_from).with(CanCan::Unauthorized).and_yield(:exception)
|
||||
@controller_class.enable_authorization { |e| @block_called = (e == :exception) }
|
||||
@block_called.should be_true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,68 +2,63 @@ require "spec_helper"
|
|||
|
||||
describe CanCan::ControllerResource do
|
||||
before(:each) do
|
||||
Project.delete_all
|
||||
Category.delete_all
|
||||
@params = HashWithIndifferentAccess.new(:controller => "projects")
|
||||
@controller_class = Class.new
|
||||
@controller = @controller_class.new
|
||||
@ability = Ability.new(nil)
|
||||
stub(@controller).params { @params }
|
||||
stub(@controller).current_ability { @ability }
|
||||
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
||||
@controller.stub(:params) { @params }
|
||||
@controller.stub(:current_ability) { @ability }
|
||||
@controller.stub(:authorize!) { |*args| @ability.authorize!(*args) }
|
||||
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
|
||||
end
|
||||
|
||||
it "should load the resource into an instance variable if params[:id] is specified" do
|
||||
it "loads the resource into an instance variable if params[:id] is specified" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should not load resource into an instance variable if already set" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
it "does not load resource into an instance variable if already set" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should properly load resource for namespaced controller" do
|
||||
it "loads resource for namespaced controller" do
|
||||
project = Project.create!
|
||||
@params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should attempt to load a resource with the same namespace as the controller when using :: for namespace" do
|
||||
module MyEngine
|
||||
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do
|
||||
module SomeEngine
|
||||
class Project < ::Project; end
|
||||
end
|
||||
|
||||
project = MyEngine::Project.create!
|
||||
@params.merge!(:controller => "MyEngine::ProjectsController", :action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
project = SomeEngine::Project.create!
|
||||
@params.merge!(:controller => "SomeEngine::ProjectsController", :action => "show", :id => project.id)
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
# Rails includes namespace in params, see issue #349
|
||||
it "should create through the namespaced params" do
|
||||
module MyEngine
|
||||
it "creates through the namespaced params" do
|
||||
module SomeEngine
|
||||
class Project < ::Project; end
|
||||
end
|
||||
|
||||
@params.merge!(:controller => "MyEngine::ProjectsController", :action => "create", :my_engine_project => {:name => "foobar"})
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@params.merge!(:controller => "SomeEngine::ProjectsController", :action => "create", :some_engine_project => {:name => "foobar"})
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should properly load resource for namespaced controller when using '::' for namespace" do
|
||||
it "loads resource for namespaced controller when using '::' for namespace" do
|
||||
project = Project.create!
|
||||
@params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
|
@ -73,419 +68,484 @@ describe CanCan::ControllerResource do
|
|||
end
|
||||
@ability.can(:index, "admin/dashboard")
|
||||
@params.merge!(:controller => "admin/dashboard", :action => "index")
|
||||
@controller.authorize!(:index, "admin/dashboard")
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
|
||||
resource.send(:resource_class).should == Admin::Dashboard
|
||||
end
|
||||
|
||||
it "should build a new resource with hash if params[:id] is not specified" do
|
||||
it "builds a new resource with hash if params[:id] is not specified and authorize on each attribute" do
|
||||
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should build a new resource for namespaced model with hash if params[:id] is not specified" do
|
||||
@params.merge!(:action => "create", 'sub_project' => {:name => "foobar"})
|
||||
resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
|
||||
resource.load_resource
|
||||
it "builds a new resource for namespaced model with hash if params[:id] is not specified" do
|
||||
module SomeEngine
|
||||
class Project < ::Project; end
|
||||
end
|
||||
@params.merge!(:action => "create", :some_engine_project => {:name => "foobar"})
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :class => SomeEngine::Project).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should build a new resource for namespaced controller and namespaced model with hash if params[:id] is not specified" do
|
||||
@params.merge!(:controller => "Admin::SubProjectsController", :action => "create", 'sub_project' => {:name => "foobar"})
|
||||
resource = CanCan::ControllerResource.new(@controller, :class => Project)
|
||||
resource.load_resource
|
||||
@controller.instance_variable_get(:@sub_project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should build a new resource with attributes from current ability" do
|
||||
it "builds a new resource with attributes from current ability" do
|
||||
@params.merge!(:action => "new")
|
||||
@ability.can(:create, Project, :name => "from conditions")
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@ability.can(:create, :projects, :name => "from conditions")
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "from conditions"
|
||||
end
|
||||
|
||||
it "should override initial attributes with params" do
|
||||
it "overrides initial attributes with params" do
|
||||
@params.merge!(:action => "new", :project => {:name => "from params"})
|
||||
@ability.can(:create, Project, :name => "from conditions")
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@ability.can(:create, :projects, :name => "from conditions")
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "from params"
|
||||
end
|
||||
|
||||
it "should build a collection when on index action when class responds to accessible_by" do
|
||||
stub(Project).accessible_by(@ability, :index) { :found_projects }
|
||||
it "builds a collection when on index action when class responds to accessible_by and mark ability as fully authorized" do
|
||||
Project.stub(:accessible_by).with(@ability, :index) { :found_projects }
|
||||
@params[:action] = "index"
|
||||
resource = CanCan::ControllerResource.new(@controller, :project)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :project, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_get(:@projects).should == :found_projects
|
||||
@ability.should be_fully_authorized(:index, :projects)
|
||||
end
|
||||
|
||||
it "should not build a collection when on index action when class does not respond to accessible_by" do
|
||||
it "does not build a collection when on index action when class does not respond to accessible_by and not mark ability as fully authorized" do
|
||||
class CustomModel
|
||||
end
|
||||
@params[:controller] = "custom_models"
|
||||
@params[:action] = "index"
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_defined?(:@projects).should be_false
|
||||
@ability.should_not be_fully_authorized(:index, :projects)
|
||||
end
|
||||
|
||||
it "does not use accessible_by when defining abilities through a block" do
|
||||
Project.stub(:accessible_by).with(@ability) { :found_projects }
|
||||
@params[:action] = "index"
|
||||
@ability.can(:read, :projects) { |p| false }
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_defined?(:@projects).should be_false
|
||||
end
|
||||
|
||||
it "should not use accessible_by when defining abilities through a block" do
|
||||
stub(Project).accessible_by(@ability) { :found_projects }
|
||||
@params[:action] = "index"
|
||||
@ability.can(:read, Project) { |p| false }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_defined?(:@projects).should be_false
|
||||
end
|
||||
|
||||
it "should not authorize single resource in collection action" do
|
||||
it "does not authorize resource in collection action" do
|
||||
@params[:action] = "index"
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
stub(@controller).authorize!(:index, Project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:index, :projects) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
|
||||
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should authorize parent resource in collection action" do
|
||||
it "authorizes parent resource in collection action" do
|
||||
@params[:action] = "index"
|
||||
@controller.instance_variable_set(:@category, :some_category)
|
||||
stub(@controller).authorize!(:show, :some_category) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:show, :some_category) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true, :authorize => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should perform authorization using controller action and loaded model" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
it "performs authorization using controller action and loaded model" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:show, :some_project) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should perform authorization using controller action and non loaded model" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
stub(@controller).authorize!(:show, Project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
it "does not perform authorization using controller action when no loaded model" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.stub(:authorize!).with(:show, :projects) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
|
||||
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
mock(resource).load_resource
|
||||
mock(resource).authorize_resource
|
||||
resource.load_and_authorize_resource
|
||||
end
|
||||
|
||||
it "should not build a single resource when on custom collection action even with id" do
|
||||
@params.merge!(:action => "sort", :id => "123")
|
||||
resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
|
||||
resource.load_resource
|
||||
it "does not build a single resource when on custom collection action even with id" do
|
||||
@params.merge!(:action => "sort", :id => 123)
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :collection => [:sort, :list]).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
end
|
||||
|
||||
it "should load a collection resource when on custom action with no id param" do
|
||||
stub(Project).accessible_by(@ability, :sort) { :found_projects }
|
||||
it "loads a collection resource when on custom action with no id param" do
|
||||
Project.stub(:accessible_by).with(@ability, :sort) { :found_projects }
|
||||
@params[:action] = "sort"
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_get(:@projects).should == :found_projects
|
||||
end
|
||||
|
||||
it "should build a resource when on custom new action even when params[:id] exists" do
|
||||
@params.merge!(:action => "build", :id => "123")
|
||||
stub(Project).new { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :new => :build)
|
||||
resource.load_resource
|
||||
it "builds a resource when on custom new action even when params[:id] exists" do
|
||||
@params.merge!(:action => "build", :id => 123)
|
||||
Project.stub(:new) { :some_project }
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :new => :build).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should not try to load resource for other action if params[:id] is undefined" do
|
||||
it "does not try to load resource for other action if params[:id] is undefined" do
|
||||
@params[:action] = "list"
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
end
|
||||
|
||||
it "should be a parent resource when name is provided which doesn't match controller" do
|
||||
it "is a parent resource when name is provided which doesn't match controller" do
|
||||
resource = CanCan::ControllerResource.new(@controller, :category)
|
||||
resource.should be_parent
|
||||
end
|
||||
|
||||
it "should not be a parent resource when name is provided which matches controller" do
|
||||
it "does not be a parent resource when name is provided which matches controller" do
|
||||
resource = CanCan::ControllerResource.new(@controller, :project)
|
||||
resource.should_not be_parent
|
||||
end
|
||||
|
||||
it "should be parent if specified in options" do
|
||||
it "is parent if specified in options" do
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, {:parent => true})
|
||||
resource.should be_parent
|
||||
end
|
||||
|
||||
it "should not be parent if specified in options" do
|
||||
it "does not be parent if specified in options" do
|
||||
resource = CanCan::ControllerResource.new(@controller, :category, {:parent => false})
|
||||
resource.should_not be_parent
|
||||
end
|
||||
|
||||
it "should have the specified resource_class if 'name' is passed to load_resource" do
|
||||
class Section
|
||||
it "has the specified resource_class if name is passed to load_resource" do
|
||||
resource = CanCan::ControllerResource.new(@controller, :category)
|
||||
resource.send(:resource_class).should == Category
|
||||
end
|
||||
|
||||
resource = CanCan::ControllerResource.new(@controller, :section)
|
||||
resource.send(:resource_class).should == Section
|
||||
end
|
||||
|
||||
it "should load parent resource through proper id parameter" do
|
||||
it "loads parent resource through proper id parameter" do
|
||||
project = Project.create!
|
||||
@params.merge!(:controller => "categories", :action => "index", :project_id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :project)
|
||||
resource.load_resource
|
||||
@params.merge!(:action => "index", :project_id => project.id)
|
||||
CanCan::ControllerResource.new(@controller, :project, :load => true, :parent => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should load resource through the association of another parent resource using instance variable" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
category = Object.new
|
||||
it "loads resource through the association of another parent resource using instance variable" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
category = double("category", :projects => double("projects"))
|
||||
category.projects.stub(:find).with(123) { :some_project }
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).projects.stub!.find("123") { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should load resource through the custom association name" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
category = Object.new
|
||||
it "loads resource through the custom association name" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
category = double("category", :custom_projects => double("custom_projects"))
|
||||
category.custom_projects.stub(:find).with(123) { :some_project }
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).custom_projects.stub!.find("123") { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :through_association => :custom_projects)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :through_association => :custom_projects).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should load resource through the association of another parent resource using method" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
category = Object.new
|
||||
stub(@controller).category { category }
|
||||
stub(category).projects.stub!.find("123") { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category)
|
||||
resource.load_resource
|
||||
it "loads resource through the association of another parent resource using method" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
category = double("category", :projects => double("projects"))
|
||||
@controller.stub(:category) { category }
|
||||
category.projects.stub(:find).with(123) { :some_project }
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should not load through parent resource if instance isn't loaded when shallow" do
|
||||
it "does not load through parent resource if instance isn't loaded when shallow" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :shallow => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should raise AccessDenied when attempting to load resource through nil" do
|
||||
it "raises Unauthorized when attempting to load resource through nil" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category)
|
||||
resource = CanCan::ControllerResource.new(@controller, :load => true, :through => :category)
|
||||
lambda {
|
||||
resource.load_resource
|
||||
}.should raise_error(CanCan::AccessDenied) { |exception|
|
||||
resource.process
|
||||
}.should raise_error(CanCan::Unauthorized) { |exception|
|
||||
exception.action.should == :show
|
||||
exception.subject.should == Project
|
||||
exception.subject.should == :projects
|
||||
}
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
end
|
||||
|
||||
it "should authorize nested resource through parent association on index action" do
|
||||
it "named resources should be loaded independently of the controller name" do
|
||||
category = Category.create!
|
||||
@params.merge!(:action => "new", :category_id => category.id)
|
||||
CanCan::ControllerResource.new(@controller, :category, :load => true).process
|
||||
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category).process
|
||||
@controller.instance_variable_get(:@category).should eq(category)
|
||||
project = @controller.instance_variable_get(:@project)
|
||||
project.category.should eq(category)
|
||||
end
|
||||
|
||||
it "parent resources shouldn't be altered" do
|
||||
category = Category.create!
|
||||
@params.merge!(:action => "create", :category_id => category.id, :project => { :name => 'foo' })
|
||||
CanCan::ControllerResource.new(@controller, :category, :load => true).process
|
||||
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category).process
|
||||
project = @controller.instance_variable_get(:@project)
|
||||
project.should be_new_record
|
||||
project.name.should eq('foo')
|
||||
end
|
||||
|
||||
it "does not overrides an attribute if it is based on parent resource" do
|
||||
user = double('user')
|
||||
user.should_receive(:category_id).and_return nil
|
||||
@ability = Ability.new user
|
||||
@ability.can :new, :projects, :category_id => user.category_id
|
||||
category = Category.create!
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
|
||||
@params.merge! :action => 'new', :category_id => category.id
|
||||
CanCan::ControllerResource.new(@controller, :category, :load => true).process
|
||||
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category, :singleton => true).process
|
||||
project = @controller.instance_variable_get(:@project)
|
||||
project.category_id.should_not be_nil
|
||||
project.category.should eq(category)
|
||||
end
|
||||
|
||||
it "authorizes nested resource through parent association on index action" do
|
||||
pending
|
||||
@params.merge!(:action => "index")
|
||||
category = Object.new
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(@controller).authorize!(:index, category => Project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:index, category => :projects) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true, :through => :category)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should load through first matching if multiple are given" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
category = Object.new
|
||||
it "loads through first matching if multiple are given" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
category = double("category", :projects => double("projects"))
|
||||
category.projects.stub(:find).with(123) { :some_project }
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).projects.stub!.find("123") { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => [:category, :user])
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => [:category, :user]).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should find record through has_one association with :singleton option without id param" do
|
||||
it "finds record through has_one association with :singleton option without id param" do
|
||||
@params.merge!(:action => "show", :id => nil)
|
||||
category = Object.new
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).project { :some_project }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
|
||||
resource.load_resource
|
||||
category.stub(:project) { :some_project }
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should not build record through has_one association with :singleton option because it can cause it to delete it in the database" do
|
||||
it "does not build record through has_one association with :singleton option because it can cause it to delete it in the database" do
|
||||
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
||||
category = Category.new
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
@controller.instance_variable_get(:@project).category.should == category
|
||||
end
|
||||
|
||||
it "should find record through has_one association with :singleton and :shallow options" do
|
||||
it "finds record through has_one association with :singleton and :shallow options" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should build record through has_one association with :singleton and :shallow options" do
|
||||
it "builds record through has_one association with :singleton and :shallow options" do
|
||||
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should only authorize :show action on parent resource" do
|
||||
it "only authorizes :show action on parent resource" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "new", :project_id => project.id)
|
||||
stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
|
||||
lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true, :parent => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should load the model using a custom class" do
|
||||
it "authorizes update action before setting attributes" do
|
||||
@ability.can :update, :projects, :name => "bar"
|
||||
project = Project.create!(:name => "foo")
|
||||
@params.merge!(:action => "update", :id => project.id, :project => {:name => "bar"})
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "authorizes update action after setting attributes" do
|
||||
@ability.can :update, :projects, :name => "foo"
|
||||
project = Project.create!(:name => "foo")
|
||||
@params.merge!(:action => "update", :id => project.id, :project => {:name => "bar"})
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "loads the model using a custom class" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :class => Project)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :class => Project).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should load the model using a custom namespaced class" do
|
||||
project = Sub::Project.create!
|
||||
it "loads the model using a custom namespaced class" do
|
||||
module SomeEngine
|
||||
class Project < ::Project; end
|
||||
end
|
||||
project = SomeEngine::Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :class => SomeEngine::Project).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should authorize based on resource name if class is false" do
|
||||
@params.merge!(:action => "show", :id => "123")
|
||||
stub(@controller).authorize!(:show, :project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :class => false)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
it "does not authorize based on resource name if class is false because we don't do class level authorization anymore" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.stub(:authorize!).with(:show, :projects) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true, :class => false)
|
||||
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should load and authorize using custom instance name" do
|
||||
it "loads and authorize using custom instance name" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :id => project.id)
|
||||
stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project)
|
||||
lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :load => true, :authorize => true, :instance_name => :custom_project)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
@controller.instance_variable_get(:@custom_project).should == project
|
||||
end
|
||||
|
||||
it "should load resource using custom ID param" do
|
||||
it "loads resource using custom ID param" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "show", :the_project => project.id)
|
||||
resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
|
||||
resource.load_resource
|
||||
resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project, :load => true)
|
||||
resource.process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
# CVE-2012-5664
|
||||
it "should always convert id param to string" do
|
||||
@params.merge!(:action => "show", :the_project => { :malicious => "I am" })
|
||||
resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
|
||||
resource.send(:id_param).class.should == String
|
||||
end
|
||||
|
||||
it "should load resource using custom find_by attribute" do
|
||||
it "loads resource using custom find_by attribute" do
|
||||
project = Project.create!(:name => "foo")
|
||||
@params.merge!(:action => "show", :id => "foo")
|
||||
resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :load => true, :find_by => :name).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should allow full find method to be passed into find_by option" do
|
||||
it "authorizes each new attribute in the create action" do
|
||||
@params.merge!(:action => "create", :project => {:name => "foo"})
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
@ability.should_receive(:authorize!).with(:create, :some_project, :name)
|
||||
CanCan::ControllerResource.new(@controller, :authorize => true).process
|
||||
end
|
||||
|
||||
it "allows full find method to be passed into find_by option" do
|
||||
project = Project.create!(:name => "foo")
|
||||
@params.merge!(:action => "show", :id => "foo")
|
||||
resource = CanCan::ControllerResource.new(@controller, :find_by => :find_by_name)
|
||||
resource.load_resource
|
||||
CanCan::ControllerResource.new(@controller, :find_by => :find_by_name, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should raise ImplementationRemoved when adding :name option" do
|
||||
lambda {
|
||||
CanCan::ControllerResource.new(@controller, :name => :foo)
|
||||
}.should raise_error(CanCan::ImplementationRemoved)
|
||||
it "authorizes each new attribute in the update action" do
|
||||
@params.merge!(:action => "update", :id => 123, :project => {:name => "foo"})
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
@ability.should_receive(:authorize!).with(:update, :some_project, :name)
|
||||
CanCan::ControllerResource.new(@controller, :authorize => true).process
|
||||
end
|
||||
|
||||
it "should raise ImplementationRemoved exception when specifying :resource option since it is no longer used" do
|
||||
lambda {
|
||||
CanCan::ControllerResource.new(@controller, :resource => Project)
|
||||
}.should raise_error(CanCan::ImplementationRemoved)
|
||||
it "fetches member through method when instance variable is not provided" do
|
||||
@controller.stub(:project) { :some_project }
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.stub(:authorize!).with(:show, :some_project) { raise CanCan::Unauthorized }
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
|
||||
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should raise ImplementationRemoved exception when passing :nested option" do
|
||||
lambda {
|
||||
CanCan::ControllerResource.new(@controller, :nested => :project)
|
||||
}.should raise_error(CanCan::ImplementationRemoved)
|
||||
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do
|
||||
module Namespaced
|
||||
class Project < ::Project; end
|
||||
end
|
||||
project = Namespaced::Project.create!
|
||||
@params.merge!(:controller => "Namespaced::ProjectsController", :action => "show", :id => project.id)
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == project
|
||||
end
|
||||
|
||||
it "should skip resource behavior for :only actions in array" do
|
||||
stub(@controller_class).cancan_skipper { {:load => {nil => {:only => [:index, :show]}}} }
|
||||
@params.merge!(:action => "index")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
||||
@params.merge!(:action => "show")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
@params.merge!(:action => "other_action")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
# Rails includes namespace in params, see issue #349
|
||||
it "creates through namespaced params" do
|
||||
module Namespaced
|
||||
class Project < ::Project; end
|
||||
end
|
||||
@params.merge!(:controller => "Namespaced::ProjectsController", :action => "create", :namespaced_project => {:name => "foobar"})
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should skip resource behavior for :only one action on resource" do
|
||||
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:only => :index}}} }
|
||||
@params.merge!(:action => "index")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
||||
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
||||
@params.merge!(:action => "other_action")
|
||||
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
||||
it "should properly authorize resource for namespaced controller" do
|
||||
@ability.can(:index, "admin/dashboard")
|
||||
@params.merge!(:controller => "admin/dashboard", :action => "index")
|
||||
@controller.authorize!(:index, "admin/dashboard")
|
||||
resource = CanCan::ControllerResource.new(@controller, :authorize => true).process
|
||||
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized)
|
||||
end
|
||||
|
||||
it "should skip resource behavior :except actions in array" do
|
||||
stub(@controller_class).cancan_skipper { {:load => {nil => {:except => [:index, :show]}}} }
|
||||
@params.merge!(:action => "index")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
@params.merge!(:action => "show")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
@params.merge!(:action => "other_action")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
||||
end
|
||||
# it "raises ImplementationRemoved when adding :name option" do
|
||||
# lambda {
|
||||
# CanCan::ControllerResource.new(@controller, :name => :foo)
|
||||
# }.should raise_error(CanCan::ImplementationRemoved)
|
||||
# end
|
||||
#
|
||||
# it "raises ImplementationRemoved exception when specifying :resource option since it is no longer used" do
|
||||
# lambda {
|
||||
# CanCan::ControllerResource.new(@controller, :resource => Project)
|
||||
# }.should raise_error(CanCan::ImplementationRemoved)
|
||||
# end
|
||||
#
|
||||
# it "raises ImplementationRemoved exception when passing :nested option" do
|
||||
# lambda {
|
||||
# CanCan::ControllerResource.new(@controller, :nested => :project)
|
||||
# }.should raise_error(CanCan::ImplementationRemoved)
|
||||
# end
|
||||
|
||||
it "should skip resource behavior :except one action on resource" do
|
||||
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:except => :index}}} }
|
||||
@params.merge!(:action => "index")
|
||||
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
||||
@params.merge!(:action => "other_action")
|
||||
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
||||
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
||||
end
|
||||
|
||||
it "should skip loading and authorization" do
|
||||
stub(@controller_class).cancan_skipper { {:authorize => {nil => {}}, :load => {nil => {}}} }
|
||||
@params.merge!(:action => "new")
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
lambda { resource.load_and_authorize_resource }.should_not raise_error
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
end
|
||||
# it "skips resource behavior for :only actions in array" do
|
||||
# @controller_class.stub(:cancan_skipper) { {:load => {nil => {:only => [:index, :show]}}} }
|
||||
# @params.merge!(:action => "index")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
# CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
||||
# @params.merge!(:action => "show")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
# @params.merge!(:action => "other_action")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
# end
|
||||
#
|
||||
# it "skips resource behavior for :only one action on resource" do
|
||||
# @controller_class.stub(:cancan_skipper) { {:authorize => {:project => {:only => :index}}} }
|
||||
# @params.merge!(:action => "index")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
||||
# CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
||||
# @params.merge!(:action => "other_action")
|
||||
# CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
||||
# end
|
||||
#
|
||||
# it "skips resource behavior :except actions in array" do
|
||||
# @controller_class.stub(:cancan_skipper) { {:load => {nil => {:except => [:index, :show]}}} }
|
||||
# @params.merge!(:action => "index")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
# @params.merge!(:action => "show")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
||||
# @params.merge!(:action => "other_action")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
||||
# CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
||||
# end
|
||||
#
|
||||
# it "skips resource behavior :except one action on resource" do
|
||||
# @controller_class.stub(:cancan_skipper) { {:authorize => {:project => {:except => :index}}} }
|
||||
# @params.merge!(:action => "index")
|
||||
# CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
||||
# @params.merge!(:action => "other_action")
|
||||
# CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
||||
# CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
||||
# end
|
||||
#
|
||||
# it "skips loading and authorization" do
|
||||
# @controller_class.stub(:cancan_skipper) { {:authorize => {nil => {}}, :load => {nil => {}}} }
|
||||
# @params.merge!(:action => "new")
|
||||
# resource = CanCan::ControllerResource.new(@controller)
|
||||
# lambda { resource.load_and_authorize_resource }.should_not raise_error
|
||||
# @controller.instance_variable_get(:@project).should be_nil
|
||||
# end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe CanCan::AccessDenied do
|
||||
describe CanCan::Unauthorized do
|
||||
describe "with action and subject" do
|
||||
before(:each) do
|
||||
@exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject)
|
||||
@exception = CanCan::Unauthorized.new(nil, :some_action, :some_subject)
|
||||
end
|
||||
|
||||
it "should have action and subject accessors" do
|
||||
it "has action and subject accessors" do
|
||||
@exception.action.should == :some_action
|
||||
@exception.subject.should == :some_subject
|
||||
end
|
||||
|
||||
it "should have a changable default message" do
|
||||
it "has a changable default message" do
|
||||
@exception.message.should == "You are not authorized to access this page."
|
||||
@exception.default_message = "Unauthorized!"
|
||||
@exception.message.should == "Unauthorized!"
|
||||
|
@ -20,15 +20,15 @@ describe CanCan::AccessDenied do
|
|||
|
||||
describe "with only a message" do
|
||||
before(:each) do
|
||||
@exception = CanCan::AccessDenied.new("Access denied!")
|
||||
@exception = CanCan::Unauthorized.new("Access denied!")
|
||||
end
|
||||
|
||||
it "should have nil action and subject" do
|
||||
it "has nil action and subject" do
|
||||
@exception.action.should be_nil
|
||||
@exception.subject.should be_nil
|
||||
end
|
||||
|
||||
it "should have passed message" do
|
||||
it "has passed message" do
|
||||
@exception.message.should == "Access denied!"
|
||||
end
|
||||
end
|
||||
|
@ -40,17 +40,17 @@ describe CanCan::AccessDenied do
|
|||
|
||||
it "uses i18n for the default message" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:default => "This is a different message"}
|
||||
@exception = CanCan::AccessDenied.new
|
||||
@exception = CanCan::Unauthorized.new
|
||||
@exception.message.should == "This is a different message"
|
||||
end
|
||||
|
||||
it "defaults to a nice message" do
|
||||
@exception = CanCan::AccessDenied.new
|
||||
@exception = CanCan::Unauthorized.new
|
||||
@exception.message.should == "You are not authorized to access this page."
|
||||
end
|
||||
|
||||
it "does not use translation if a message is given" do
|
||||
@exception = CanCan::AccessDenied.new("Hey! You're not welcome here")
|
||||
@exception = CanCan::Unauthorized.new("Hey! You're not welcome here")
|
||||
@exception.message.should == "Hey! You're not welcome here"
|
||||
@exception.message.should_not == "You are not authorized to access this page."
|
||||
end
|
||||
|
|
|
@ -6,55 +6,53 @@ describe CanCan::InheritedResource do
|
|||
@controller_class = Class.new
|
||||
@controller = @controller_class.new
|
||||
@ability = Ability.new(nil)
|
||||
stub(@controller).params { @params }
|
||||
stub(@controller).current_ability { @ability }
|
||||
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
||||
@controller.stub(:params) { @params }
|
||||
@controller.stub(:current_ability) { @ability }
|
||||
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
|
||||
end
|
||||
|
||||
it "show should load resource through @controller.resource" do
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
stub(@controller).resource { :project_resource }
|
||||
CanCan::InheritedResource.new(@controller).load_resource
|
||||
@controller.stub(:resource) { :project_resource }
|
||||
CanCan::InheritedResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
end
|
||||
|
||||
it "new should load through @controller.build_resource" do
|
||||
@params[:action] = "new"
|
||||
stub(@controller).build_resource { :project_resource }
|
||||
CanCan::InheritedResource.new(@controller).load_resource
|
||||
@controller.stub(:build_resource) { :project_resource }
|
||||
CanCan::InheritedResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
end
|
||||
|
||||
it "index should load through @controller.association_chain when parent" do
|
||||
@params[:action] = "index"
|
||||
stub(@controller).association_chain { @controller.instance_variable_set(:@project, :project_resource) }
|
||||
CanCan::InheritedResource.new(@controller, :parent => true).load_resource
|
||||
@controller.stub(:association_chain) { @controller.instance_variable_set(:@project, :project_resource) }
|
||||
CanCan::InheritedResource.new(@controller, :load => true, :parent => true).process
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
end
|
||||
|
||||
it "index should load through @controller.end_of_association_chain" do
|
||||
@params[:action] = "index"
|
||||
stub(Project).accessible_by(@ability, :index) { :projects }
|
||||
stub(@controller).end_of_association_chain { Project }
|
||||
CanCan::InheritedResource.new(@controller).load_resource
|
||||
Project.stub(:accessible_by).with(@ability, :index) { :projects }
|
||||
@controller.stub(:end_of_association_chain) { Project }
|
||||
CanCan::InheritedResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@projects).should == :projects
|
||||
end
|
||||
|
||||
it "should build a new resource with attributes from current ability" do
|
||||
@params[:action] = "new"
|
||||
@ability.can(:create, Project, :name => "from conditions")
|
||||
stub(@controller).build_resource { Struct.new(:name).new }
|
||||
resource = CanCan::InheritedResource.new(@controller)
|
||||
resource.load_resource
|
||||
@ability.can(:create, :projects, :name => "from conditions")
|
||||
@controller.stub(:build_resource) { Struct.new(:name).new }
|
||||
CanCan::InheritedResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "from conditions"
|
||||
end
|
||||
|
||||
it "should override initial attributes with params" do
|
||||
@params.merge!(:action => "new", :project => {:name => "from params"})
|
||||
@ability.can(:create, Project, :name => "from conditions")
|
||||
stub(@controller).build_resource { Struct.new(:name).new }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@ability.can(:create, :projects, :name => "from conditions")
|
||||
@controller.stub(:build_resource) { Struct.new(:name).new }
|
||||
CanCan::ControllerResource.new(@controller, :load => true).process
|
||||
@controller.instance_variable_get(:@project).name.should == "from params"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,13 @@ require "spec_helper"
|
|||
describe "be_able_to" do
|
||||
it "delegates to can?" do
|
||||
object = Object.new
|
||||
mock(object).can?(:read, 123) { true }
|
||||
object.should_receive(:can?).with(:read, 123) { true }
|
||||
object.should be_able_to(:read, 123)
|
||||
end
|
||||
|
||||
it "reports a nice failure message for should" do
|
||||
object = Object.new
|
||||
mock(object).can?(:read, 123) { false }
|
||||
object.should_receive(:can?).with(:read, 123) { false }
|
||||
expect do
|
||||
object.should be_able_to(:read, 123)
|
||||
end.should raise_error('expected to be able to :read 123')
|
||||
|
@ -17,7 +17,7 @@ describe "be_able_to" do
|
|||
|
||||
it "reports a nice failure message for should not" do
|
||||
object = Object.new
|
||||
mock(object).can?(:read, 123) { true }
|
||||
object.should_receive(:can?).with(:read, 123) { true }
|
||||
expect do
|
||||
object.should_not be_able_to(:read, 123)
|
||||
end.should raise_error('expected not to be able to :read 123')
|
||||
|
@ -25,7 +25,7 @@ describe "be_able_to" do
|
|||
|
||||
it "delegates additional arguments to can? and reports in failure message" do
|
||||
object = Object.new
|
||||
mock(object).can?(:read, 123, 456) { false }
|
||||
object.should_receive(:can?).with(:read, 123, 456) { false }
|
||||
expect do
|
||||
object.should be_able_to(:read, 123, 456)
|
||||
end.should raise_error('expected to be able to :read 123 456')
|
||||
|
|
|
@ -1,53 +1,31 @@
|
|||
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
require "spec_helper"
|
||||
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
||||
|
||||
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
||||
with_model :category do
|
||||
table do |t|
|
||||
t.boolean "visible"
|
||||
end
|
||||
model do
|
||||
class Category
|
||||
has_many :articles
|
||||
end
|
||||
end
|
||||
|
||||
with_model :article do
|
||||
table do |t|
|
||||
t.string "name"
|
||||
t.boolean "published"
|
||||
t.boolean "secret"
|
||||
t.integer "priority"
|
||||
t.integer "category_id"
|
||||
t.integer "user_id"
|
||||
class Article < ActiveRecord::Base
|
||||
connection.create_table(table_name) do |t|
|
||||
t.integer :category_id
|
||||
t.string :name
|
||||
t.boolean :published
|
||||
t.boolean :secret
|
||||
t.integer :priority
|
||||
end
|
||||
model do
|
||||
belongs_to :category
|
||||
has_many :comments
|
||||
belongs_to :user
|
||||
end
|
||||
end
|
||||
|
||||
with_model :comment do
|
||||
table do |t|
|
||||
t.boolean "spam"
|
||||
t.integer "article_id"
|
||||
class Comment < ActiveRecord::Base
|
||||
connection.create_table(table_name) do |t|
|
||||
t.integer :article_id
|
||||
t.boolean :spam
|
||||
end
|
||||
model do
|
||||
belongs_to :article
|
||||
end
|
||||
end
|
||||
|
||||
with_model :user do
|
||||
table do |t|
|
||||
|
||||
end
|
||||
model do
|
||||
has_many :articles
|
||||
end
|
||||
end
|
||||
|
||||
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
||||
before(:each) do
|
||||
Article.delete_all
|
||||
Comment.delete_all
|
||||
|
@ -57,38 +35,38 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
@comment_table = Comment.table_name
|
||||
end
|
||||
|
||||
it "should be for only active record classes" do
|
||||
it "is for only active record classes" do
|
||||
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
|
||||
CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
|
||||
end
|
||||
|
||||
it "should find record" do
|
||||
it "finds record" do
|
||||
article = Article.create!
|
||||
CanCan::ModelAdapters::ActiveRecordAdapter.find(Article, article.id).should == article
|
||||
end
|
||||
|
||||
it "should not fetch any records when no abilities are defined" do
|
||||
it "does not fetch any records when no abilities are defined" do
|
||||
Article.create!
|
||||
Article.accessible_by(@ability).should be_empty
|
||||
end
|
||||
|
||||
it "should fetch all articles when one can read all" do
|
||||
@ability.can :read, Article
|
||||
it "fetches all articles when one can read all" do
|
||||
@ability.can :read, :articles
|
||||
article = Article.create!
|
||||
Article.accessible_by(@ability).should == [article]
|
||||
end
|
||||
|
||||
it "should fetch only the articles that are published" do
|
||||
@ability.can :read, Article, :published => true
|
||||
it "fetches only the articles that are published" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
article1 = Article.create!(:published => true)
|
||||
article2 = Article.create!(:published => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should fetch any articles which are published or secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, :secret => true
|
||||
it "fetches any articles which are published or secret" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
@ability.can :read, :articles, :secret => true
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
|
@ -96,9 +74,9 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
Article.accessible_by(@ability).should == [article1, article2, article3]
|
||||
end
|
||||
|
||||
it "should fetch only the articles that are published and not secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.cannot :read, Article, :secret => true
|
||||
it "fetches only the articles that are published and not secret" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
@ability.cannot :read, :articles, :secret => true
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
|
@ -106,23 +84,24 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should only read comments for articles which are published" do
|
||||
@ability.can :read, Comment, :article => { :published => true }
|
||||
it "only reads comments for articles which are published" do
|
||||
@ability.can :read, :comments, :article => { :published => true }
|
||||
comment1 = Comment.create!(:article => Article.create!(:published => true))
|
||||
comment2 = Comment.create!(:article => Article.create!(:published => false))
|
||||
Comment.accessible_by(@ability).should == [comment1]
|
||||
end
|
||||
|
||||
it "should only read comments for visible categories through articles" do
|
||||
@ability.can :read, Comment, :article => { :category => { :visible => true } }
|
||||
it "only reads comments for visible categories through articles" do
|
||||
pending "does ActiveRecord no longer support a deep nested hash of conditions?"
|
||||
@ability.can :read, :comments, :article => { :category => { :visible => true } }
|
||||
comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true)))
|
||||
comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false)))
|
||||
Comment.accessible_by(@ability).should == [comment1]
|
||||
end
|
||||
|
||||
it "should allow conditions in SQL and merge with hash conditions" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
it "allows conditions in SQL and merge with hash conditions" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
@ability.can :read, :articles, ["secret=?", true]
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
|
@ -130,141 +109,118 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
Article.accessible_by(@ability).should == [article1, article2, article3]
|
||||
end
|
||||
|
||||
it "should allow a scope for conditions" do
|
||||
@ability.can :read, Article, Article.where(:secret => true)
|
||||
it "allows a scope for conditions" do
|
||||
@ability.can :read, :articles, Article.where(:secret => true)
|
||||
article1 = Article.create!(:secret => true)
|
||||
article2 = Article.create!(:secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should fetch only associated records when using with a scope for conditions" do
|
||||
@ability.can :read, Article, Article.where(:secret => true)
|
||||
it "fetches only associated records when using with a scope for conditions" do
|
||||
@ability.can :read, :articles, Article.where(:secret => true)
|
||||
category1 = Category.create!(:visible => false)
|
||||
category2 = Category.create!(:visible => true)
|
||||
article1 = Article.create!(:secret => true, :category => category1)
|
||||
article2 = Article.create!(:secret => true, :category => category2)
|
||||
category1.articles.accessible_by(@ability).should == [article1]
|
||||
# for some reason the objects aren't comparing equally here so it's necessary to compare by id
|
||||
category1.articles.accessible_by(@ability).map(&:id).should == [article1.id]
|
||||
end
|
||||
|
||||
it "should raise an exception when trying to merge scope with other conditions" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, Article.where(:secret => true)
|
||||
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read Article ability.")
|
||||
it "raises an exception when trying to merge scope with other conditions" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
@ability.can :read, :articles, Article.where(:secret => true)
|
||||
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read articles ability.")
|
||||
end
|
||||
|
||||
it "should not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, Article do
|
||||
it "does not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, :articles do
|
||||
false
|
||||
end
|
||||
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
|
||||
end
|
||||
|
||||
it "should not allow to check ability on object against SQL conditions without block" do
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
it "does not allow to check ability on object against SQL conditions without block" do
|
||||
@ability.can :read, :articles, ["secret=?", true]
|
||||
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
|
||||
end
|
||||
|
||||
it "should have false conditions if no abilities match" do
|
||||
it "has false conditions if no abilities match" do
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return false conditions for cannot clause" do
|
||||
@ability.cannot :read, Article
|
||||
it "returns false conditions for cannot clause" do
|
||||
@ability.cannot :read, :articles
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return SQL for single `can` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Article
|
||||
@ability.can :read, Article, :published => false, :secret => true
|
||||
it "returns SQL for single `can` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, :articles
|
||||
@ability.can :read, :articles, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
|
||||
end
|
||||
|
||||
it "should return true condition for single `can` definition in front of default `can` condition" do
|
||||
@ability.can :read, Article
|
||||
@ability.can :read, Article, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
||||
it "returns true condition for single `can` definition in front of default `can` condition" do
|
||||
@ability.can :read, :articles
|
||||
@ability.can :read, :articles, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should eq(:secret => true, :published => false)
|
||||
end
|
||||
|
||||
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Article
|
||||
@ability.cannot :read, Article, :published => false, :secret => true
|
||||
it "returns `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, :articles
|
||||
@ability.cannot :read, :articles, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
||||
@ability.can :read, Article
|
||||
@ability.cannot :read, Article, :published => false, :secret => true
|
||||
it "returns `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
||||
@ability.can :read, :articles
|
||||
@ability.cannot :read, :articles, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
|
||||
end
|
||||
|
||||
it "should return appropriate sql conditions in complex case" do
|
||||
@ability.can :read, Article
|
||||
@ability.can :manage, Article, :id => 1
|
||||
@ability.can :update, Article, :published => true
|
||||
@ability.cannot :update, Article, :secret => true
|
||||
it "returns appropriate sql conditions in complex case" do
|
||||
@ability.can :read, :articles
|
||||
@ability.can :access, :articles, :id => 1
|
||||
@ability.can :update, :articles, :published => true
|
||||
@ability.cannot :update, :articles, :secret => true
|
||||
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
|
||||
@ability.model_adapter(Article, :manage).conditions.should == {:id => 1}
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
||||
@ability.model_adapter(Article, :access).conditions.should == {:id => 1}
|
||||
@ability.model_adapter(Article, :read).conditions.should == {:id => 1} # used to be "t=t" but changed with new specificity rule (issue #321)
|
||||
end
|
||||
|
||||
it "should return appropriate sql conditions in complex case with nested joins" do
|
||||
@ability.can :read, Comment, :article => { :category => { :visible => true } }
|
||||
@ability.model_adapter(Comment, :read).conditions.should == { Category.table_name.to_sym => { :visible => true } }
|
||||
end
|
||||
|
||||
it "should return appropriate sql conditions in complex case with nested joins of different depth" do
|
||||
@ability.can :read, Comment, :article => { :published => true, :category => { :visible => true } }
|
||||
@ability.model_adapter(Comment, :read).conditions.should == { Article.table_name.to_sym => { :published => true }, Category.table_name.to_sym => { :visible => true } }
|
||||
end
|
||||
|
||||
it "should not forget conditions when calling with SQL string" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ['secret=?', false]
|
||||
it "does not forget conditions when calling with SQL string" do
|
||||
@ability.can :read, :articles, :published => true
|
||||
@ability.can :read, :articles, ['secret=?', false]
|
||||
adapter = @ability.model_adapter(Article, :read)
|
||||
2.times do
|
||||
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
|
||||
end
|
||||
end
|
||||
|
||||
it "should have nil joins if no rules" do
|
||||
it "has nil joins if no rules" do
|
||||
@ability.model_adapter(Article, :read).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should have nil joins if no nested hashes specified in conditions" do
|
||||
@ability.can :read, Article, :published => false
|
||||
@ability.can :read, Article, :secret => true
|
||||
it "has nil joins if no nested hashes specified in conditions" do
|
||||
@ability.can :read, :articles, :published => false
|
||||
@ability.can :read, :articles, :secret => true
|
||||
@ability.model_adapter(Article, :read).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should merge separate joins into a single array" do
|
||||
@ability.can :read, Article, :project => { :blocked => false }
|
||||
@ability.can :read, Article, :company => { :admin => true }
|
||||
it "merges separate joins into a single array" do
|
||||
@ability.can :read, :articles, :project => { :blocked => false }
|
||||
@ability.can :read, :articles, :company => { :admin => true }
|
||||
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
|
||||
end
|
||||
|
||||
it "should merge same joins into a single array" do
|
||||
@ability.can :read, Article, :project => { :blocked => false }
|
||||
@ability.can :read, Article, :project => { :admin => true }
|
||||
it "merges same joins into a single array" do
|
||||
@ability.can :read, :articles, :project => { :blocked => false }
|
||||
@ability.can :read, :articles, :project => { :admin => true }
|
||||
@ability.model_adapter(Article, :read).joins.should == [:project]
|
||||
end
|
||||
|
||||
it "should merge nested and non-nested joins" do
|
||||
@ability.can :read, Article, :project => { :blocked => false }
|
||||
@ability.can :read, Article, :project => { :comments => { :spam => true } }
|
||||
@ability.model_adapter(Article, :read).joins.should == [{:project=>[:comments]}]
|
||||
end
|
||||
|
||||
it "should merge :all conditions with other conditions" do
|
||||
user = User.create!
|
||||
article = Article.create!(:user => user)
|
||||
ability = Ability.new(user)
|
||||
ability.can :manage, :all
|
||||
ability.can :manage, Article, :user_id => user.id
|
||||
Article.accessible_by(ability).should == [article]
|
||||
end
|
||||
|
||||
it "should restrict articles given a MetaWhere condition" do
|
||||
@ability.can :read, Article, :priority.lt => 2
|
||||
it "restricts articles given a MetaWhere condition" do
|
||||
pending
|
||||
@ability.can :read, :articles, :priority.lt => 2
|
||||
article1 = Article.create!(:priority => 1)
|
||||
article2 = Article.create!(:priority => 3)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
|
@ -273,6 +229,7 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
end
|
||||
|
||||
it "should merge MetaWhere and non-MetaWhere conditions" do
|
||||
pending
|
||||
@ability.can :read, Article, :priority.lt => 2
|
||||
@ability.can :read, Article, :priority => 1
|
||||
article1 = Article.create!(:priority => 1)
|
||||
|
@ -282,7 +239,8 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|||
@ability.should_not be_able_to(:read, article2)
|
||||
end
|
||||
|
||||
it "should match any MetaWhere condition" do
|
||||
it "matches any MetaWhere condition" do
|
||||
pending
|
||||
adapter = CanCan::ModelAdapters::ActiveRecordAdapter
|
||||
article1 = Article.new(:priority => 1, :name => "Hello World")
|
||||
adapter.matches_condition?(article1, :priority.eq, 1).should be_true
|
||||
|
|
|
@ -3,20 +3,20 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
|
|||
|
||||
DataMapper.setup(:default, 'sqlite::memory:')
|
||||
|
||||
class Article
|
||||
class DataMapperArticle
|
||||
include DataMapper::Resource
|
||||
property :id, Serial
|
||||
property :published, Boolean, :default => false
|
||||
property :secret, Boolean, :default => false
|
||||
property :priority, Integer
|
||||
has n, :comments
|
||||
has n, :data_mapper_comments
|
||||
end
|
||||
|
||||
class Comment
|
||||
class DataMapperComment
|
||||
include DataMapper::Resource
|
||||
property :id, Serial
|
||||
property :spam, Boolean, :default => false
|
||||
belongs_to :article
|
||||
belongs_to :data_mapper_article
|
||||
end
|
||||
|
||||
DataMapper.finalize
|
||||
|
@ -24,91 +24,92 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
|
|||
|
||||
describe CanCan::ModelAdapters::DataMapperAdapter do
|
||||
before(:each) do
|
||||
Article.destroy
|
||||
Comment.destroy
|
||||
DataMapperArticle.destroy
|
||||
DataMapperComment.destroy
|
||||
@ability = Object.new
|
||||
@ability.extend(CanCan::Ability)
|
||||
end
|
||||
|
||||
it "should be for only data mapper classes" do
|
||||
it "is for only data mapper classes" do
|
||||
CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object)
|
||||
CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(Article)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::DataMapperAdapter
|
||||
CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(DataMapperArticle)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(DataMapperArticle).should == CanCan::ModelAdapters::DataMapperAdapter
|
||||
end
|
||||
|
||||
it "should find record" do
|
||||
article = Article.create
|
||||
CanCan::ModelAdapters::DataMapperAdapter.find(Article, article.id).should == article
|
||||
it "finds record" do
|
||||
article = DataMapperArticle.create
|
||||
CanCan::ModelAdapters::DataMapperAdapter.find(DataMapperArticle, article.id).should == article
|
||||
end
|
||||
|
||||
it "should not fetch any records when no abilities are defined" do
|
||||
Article.create
|
||||
Article.accessible_by(@ability).should be_empty
|
||||
it "does not fetch any records when no abilities are defined" do
|
||||
DataMapperArticle.create
|
||||
DataMapperArticle.accessible_by(@ability).should be_empty
|
||||
end
|
||||
|
||||
it "should fetch all articles when one can read all" do
|
||||
@ability.can :read, Article
|
||||
article = Article.create
|
||||
Article.accessible_by(@ability).should == [article]
|
||||
it "fetches all articles when one can read all" do
|
||||
@ability.can :read, :data_mapper_articles
|
||||
article = DataMapperArticle.create
|
||||
DataMapperArticle.accessible_by(@ability).should == [article]
|
||||
end
|
||||
|
||||
it "should fetch only the articles that are published" do
|
||||
@ability.can :read, Article, :published => true
|
||||
article1 = Article.create(:published => true)
|
||||
article2 = Article.create(:published => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
it "fetches only the articles that are published" do
|
||||
@ability.can :read, :data_mapper_articles, :published => true
|
||||
article1 = DataMapperArticle.create(:published => true)
|
||||
article2 = DataMapperArticle.create(:published => false)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should fetch any articles which are published or secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, :secret => true
|
||||
article1 = Article.create(:published => true, :secret => false)
|
||||
article2 = Article.create(:published => true, :secret => true)
|
||||
article3 = Article.create(:published => false, :secret => true)
|
||||
article4 = Article.create(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1, article2, article3]
|
||||
it "fetches any articles which are published or secret" do
|
||||
@ability.can :read, :data_mapper_articles, :published => true
|
||||
@ability.can :read, :data_mapper_articles, :secret => true
|
||||
article1 = DataMapperArticle.create(:published => true, :secret => false)
|
||||
article2 = DataMapperArticle.create(:published => true, :secret => true)
|
||||
article3 = DataMapperArticle.create(:published => false, :secret => true)
|
||||
article4 = DataMapperArticle.create(:published => false, :secret => false)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1, article2, article3]
|
||||
end
|
||||
|
||||
it "should fetch only the articles that are published and not secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.cannot :read, Article, :secret => true
|
||||
article1 = Article.create(:published => true, :secret => false)
|
||||
article2 = Article.create(:published => true, :secret => true)
|
||||
article3 = Article.create(:published => false, :secret => true)
|
||||
article4 = Article.create(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
it "fetches only the articles that are published and not secret" do
|
||||
pending "the `cannot` may require some custom SQL, maybe abstract out from Active Record adapter"
|
||||
@ability.can :read, :data_mapper_articles, :published => true
|
||||
@ability.cannot :read, :data_mapper_articles, :secret => true
|
||||
article1 = DataMapperArticle.create(:published => true, :secret => false)
|
||||
article2 = DataMapperArticle.create(:published => true, :secret => true)
|
||||
article3 = DataMapperArticle.create(:published => false, :secret => true)
|
||||
article4 = DataMapperArticle.create(:published => false, :secret => false)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should only read comments for articles which are published" do
|
||||
@ability.can :read, Comment, :article => { :published => true }
|
||||
comment1 = Comment.create(:article => Article.create!(:published => true))
|
||||
comment2 = Comment.create(:article => Article.create!(:published => false))
|
||||
Comment.accessible_by(@ability).should == [comment1]
|
||||
it "only reads comments for articles which are published" do
|
||||
@ability.can :read, :data_mapper_comments, :data_mapper_article => { :published => true }
|
||||
comment1 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => true))
|
||||
comment2 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => false))
|
||||
DataMapperComment.accessible_by(@ability).should == [comment1]
|
||||
end
|
||||
|
||||
it "should allow conditions in SQL and merge with hash conditions" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
article1 = Article.create(:published => true, :secret => false)
|
||||
article4 = Article.create(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
it "allows conditions in SQL and merge with hash conditions" do
|
||||
@ability.can :read, :data_mapper_articles, :published => true
|
||||
@ability.can :read, :data_mapper_articles, ["secret=?", true]
|
||||
article1 = DataMapperArticle.create(:published => true, :secret => false)
|
||||
article4 = DataMapperArticle.create(:published => false, :secret => false)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should match gt comparison" do
|
||||
@ability.can :read, Article, :priority.gt => 3
|
||||
article1 = Article.create(:priority => 4)
|
||||
article2 = Article.create(:priority => 3)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
it "matches gt comparison" do
|
||||
@ability.can :read, :data_mapper_articles, :priority.gt => 3
|
||||
article1 = DataMapperArticle.create(:priority => 4)
|
||||
article2 = DataMapperArticle.create(:priority => 3)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1]
|
||||
@ability.should be_able_to(:read, article1)
|
||||
@ability.should_not be_able_to(:read, article2)
|
||||
end
|
||||
|
||||
it "should match gte comparison" do
|
||||
@ability.can :read, Article, :priority.gte => 3
|
||||
article1 = Article.create(:priority => 4)
|
||||
article2 = Article.create(:priority => 3)
|
||||
article3 = Article.create(:priority => 2)
|
||||
Article.accessible_by(@ability).should == [article1, article2]
|
||||
it "matches gte comparison" do
|
||||
@ability.can :read, :data_mapper_articles, :priority.gte => 3
|
||||
article1 = DataMapperArticle.create(:priority => 4)
|
||||
article2 = DataMapperArticle.create(:priority => 3)
|
||||
article3 = DataMapperArticle.create(:priority => 2)
|
||||
DataMapperArticle.accessible_by(@ability).should == [article1, article2]
|
||||
@ability.should be_able_to(:read, article1)
|
||||
@ability.should be_able_to(:read, article2)
|
||||
@ability.should_not be_able_to(:read, article3)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe CanCan::ModelAdapters::DefaultAdapter do
|
||||
it "should be default for generic classes" do
|
||||
it "is default for generic classes" do
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Object).should == CanCan::ModelAdapters::DefaultAdapter
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,11 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
|
||||
class MongoidCategory
|
||||
include Mongoid::Document
|
||||
|
||||
references_many :mongoid_projects
|
||||
end
|
||||
|
||||
class MongoidProject
|
||||
include Mongoid::Document
|
||||
|
||||
referenced_in :mongoid_category
|
||||
end
|
||||
|
||||
|
@ -30,33 +28,33 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
end.each(&:drop)
|
||||
end
|
||||
|
||||
it "should be for only Mongoid classes" do
|
||||
it "is for only Mongoid classes" do
|
||||
CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
|
||||
CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
|
||||
CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
|
||||
end
|
||||
|
||||
it "should find record" do
|
||||
it "finds record" do
|
||||
project = MongoidProject.create
|
||||
CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id).should == project
|
||||
end
|
||||
|
||||
it "should compare properties on mongoid documents with the conditions hash" do
|
||||
it "compares properties on mongoid documents with the conditions hash" do
|
||||
model = MongoidProject.new
|
||||
@ability.can :read, MongoidProject, :id => model.id
|
||||
@ability.can :read, :mongoid_projects, :id => model.id
|
||||
@ability.should be_able_to(:read, model)
|
||||
end
|
||||
|
||||
it "should be able to read hashes when field is array" do
|
||||
it "is able to read hashes when field is array" do
|
||||
one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three'])
|
||||
two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five'])
|
||||
|
||||
@ability.can :foo, MongoidProject, :numbers => 'one'
|
||||
@ability.can :foo, :mongoid_projects, :numbers => 'one'
|
||||
@ability.should be_able_to(:foo, one_to_three)
|
||||
@ability.should_not be_able_to(:foo, two_to_five)
|
||||
end
|
||||
|
||||
it "should return [] when no ability is defined so no records are found" do
|
||||
it "returns [] when no ability is defined so no records are found" do
|
||||
MongoidProject.create(:title => 'Sir')
|
||||
MongoidProject.create(:title => 'Lord')
|
||||
MongoidProject.create(:title => 'Dude')
|
||||
|
@ -64,8 +62,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
MongoidProject.accessible_by(@ability, :read).entries.should == []
|
||||
end
|
||||
|
||||
it "should return the correct records based on the defined ability" do
|
||||
@ability.can :read, MongoidProject, :title => "Sir"
|
||||
it "returns the correct records based on the defined ability" do
|
||||
@ability.can :read, :mongoid_projects, :title => "Sir"
|
||||
sir = MongoidProject.create(:title => 'Sir')
|
||||
lord = MongoidProject.create(:title => 'Lord')
|
||||
dude = MongoidProject.create(:title => 'Dude')
|
||||
|
@ -73,9 +71,10 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
|
||||
end
|
||||
|
||||
it "should return the correct records when a mix of can and cannot rules in defined ability" do
|
||||
@ability.can :manage, MongoidProject, :title => 'Sir'
|
||||
@ability.cannot :destroy, MongoidProject
|
||||
it "returns the correct records when a mix of can and cannot rules in defined ability" do
|
||||
pending "TODO figure out why this isn't working"
|
||||
@ability.can :manage, :mongoid_projects, :title => 'Sir'
|
||||
@ability.cannot :destroy, :mongoid_projects
|
||||
|
||||
sir = MongoidProject.create(:title => 'Sir')
|
||||
lord = MongoidProject.create(:title => 'Lord')
|
||||
|
@ -84,17 +83,17 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
MongoidProject.accessible_by(@ability, :destroy).entries.should == [sir]
|
||||
end
|
||||
|
||||
it "should be able to mix empty conditions and hashes" do
|
||||
@ability.can :read, MongoidProject
|
||||
@ability.can :read, MongoidProject, :title => 'Sir'
|
||||
it "takes presedence over rule defined without a condition" do
|
||||
@ability.can :read, :mongoid_projects
|
||||
@ability.can :read, :mongoid_projects, :title => 'Sir'
|
||||
sir = MongoidProject.create(:title => 'Sir')
|
||||
lord = MongoidProject.create(:title => 'Lord')
|
||||
|
||||
MongoidProject.accessible_by(@ability, :read).count.should == 2
|
||||
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
|
||||
end
|
||||
|
||||
it "should return everything when the defined ability is manage all" do
|
||||
@ability.can :manage, :all
|
||||
it "returns everything when the defined ability is access all" do
|
||||
@ability.can :access, :all
|
||||
sir = MongoidProject.create(:title => 'Sir')
|
||||
lord = MongoidProject.create(:title => 'Lord')
|
||||
dude = MongoidProject.create(:title => 'Dude')
|
||||
|
@ -102,8 +101,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
|
||||
end
|
||||
|
||||
it "should allow a scope for conditions" do
|
||||
@ability.can :read, MongoidProject, MongoidProject.where(:title => 'Sir')
|
||||
it "allows a scope for conditions" do
|
||||
@ability.can :read, :mongoid_projects, MongoidProject.where(:title => 'Sir')
|
||||
sir = MongoidProject.create(:title => 'Sir')
|
||||
lord = MongoidProject.create(:title => 'Lord')
|
||||
dude = MongoidProject.create(:title => 'Dude')
|
||||
|
@ -112,9 +111,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
end
|
||||
|
||||
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
|
||||
it "should handle :field.in" do
|
||||
it "handles :field.in" do
|
||||
obj = MongoidProject.create(:title => 'Sir')
|
||||
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
|
||||
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
|
||||
@ability.can?(:read, obj).should == true
|
||||
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
|
@ -127,20 +126,20 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
obj = MongoidProject.create(:title => 'Bird')
|
||||
@conditions = {:title.nin => ["Fork", "Spoon"]}
|
||||
|
||||
@ability.can :read, MongoidProject, @conditions
|
||||
@ability.can :read, :mongoid_projects, @conditions
|
||||
@ability.should be_able_to(:read, obj)
|
||||
end
|
||||
it "Calls the base version if there are no mongoid criteria" do
|
||||
obj = MongoidProject.new(:title => 'Bird')
|
||||
@conditions = {:id => obj.id}
|
||||
@ability.can :read, MongoidProject, @conditions
|
||||
@ability.can :read, :mongoid_projects, @conditions
|
||||
@ability.should be_able_to(:read, obj)
|
||||
end
|
||||
end
|
||||
|
||||
it "should handle :field.nin" do
|
||||
it "handles :field.nin" do
|
||||
obj = MongoidProject.create(:title => 'Sir')
|
||||
@ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"]
|
||||
@ability.can :read, :mongoid_projects, :title.nin => ["Lord", "Madam"]
|
||||
@ability.can?(:read, obj).should == true
|
||||
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
|
@ -148,9 +147,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.size" do
|
||||
it "handles :field.size" do
|
||||
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
|
||||
@ability.can :read, MongoidProject, :titles.size => 2
|
||||
@ability.can :read, :mongoid_projects, :titles.size => 2
|
||||
@ability.can?(:read, obj).should == true
|
||||
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
|
@ -158,9 +157,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.exists" do
|
||||
it "handles :field.exists" do
|
||||
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
|
||||
@ability.can :read, MongoidProject, :titles.exists => true
|
||||
@ability.can :read, :mongoid_projects, :titles.exists => true
|
||||
@ability.can?(:read, obj).should == true
|
||||
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
|
@ -168,9 +167,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.gt" do
|
||||
it "handles :field.gt" do
|
||||
obj = MongoidProject.create(:age => 50)
|
||||
@ability.can :read, MongoidProject, :age.gt => 45
|
||||
@ability.can :read, :mongoid_projects, :age.gt => 45
|
||||
@ability.can?(:read, obj).should == true
|
||||
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
|
@ -178,9 +177,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle instance not saved to database" do
|
||||
it "handles instance not saved to database" do
|
||||
obj = MongoidProject.new(:title => 'Sir')
|
||||
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
|
||||
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
|
||||
@ability.can?(:read, obj).should == true
|
||||
|
||||
# accessible_by only returns saved records
|
||||
|
@ -191,31 +190,31 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
|
|||
end
|
||||
end
|
||||
|
||||
it "should call where with matching ability conditions" do
|
||||
it "calls where with matching ability conditions" do
|
||||
obj = MongoidProject.create(:foo => {:bar => 1})
|
||||
@ability.can :read, MongoidProject, :foo => {:bar => 1}
|
||||
@ability.can :read, :mongoid_projects, :foo => {:bar => 1}
|
||||
MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
|
||||
end
|
||||
|
||||
it "should exclude from the result if set to cannot" do
|
||||
it "excludes from the result if set to cannot" do
|
||||
obj = MongoidProject.create(:bar => 1)
|
||||
obj2 = MongoidProject.create(:bar => 2)
|
||||
@ability.can :read, MongoidProject
|
||||
@ability.cannot :read, MongoidProject, :bar => 2
|
||||
@ability.can :read, :mongoid_projects
|
||||
@ability.cannot :read, :mongoid_projects, :bar => 2
|
||||
MongoidProject.accessible_by(@ability, :read).entries.should == [obj]
|
||||
end
|
||||
|
||||
it "should combine the rules" do
|
||||
it "combines the rules" do
|
||||
obj = MongoidProject.create(:bar => 1)
|
||||
obj2 = MongoidProject.create(:bar => 2)
|
||||
obj3 = MongoidProject.create(:bar => 3)
|
||||
@ability.can :read, MongoidProject, :bar => 1
|
||||
@ability.can :read, MongoidProject, :bar => 2
|
||||
@ability.can :read, :mongoid_projects, :bar => 1
|
||||
@ability.can :read, :mongoid_projects, :bar => 2
|
||||
MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2]
|
||||
end
|
||||
|
||||
it "should not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, MongoidProject do
|
||||
it "does not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, :mongoid_projects do
|
||||
false
|
||||
end
|
||||
lambda {
|
||||
|
|
|
@ -5,48 +5,51 @@ require "ostruct" # for OpenStruct below
|
|||
describe CanCan::Rule do
|
||||
before(:each) do
|
||||
@conditions = {}
|
||||
@rule = CanCan::Rule.new(true, :read, Integer, @conditions, nil)
|
||||
@rule = CanCan::Rule.new(true, :read, :integers, @conditions)
|
||||
end
|
||||
|
||||
it "should return no association joins if none exist" do
|
||||
it "returns no association joins if none exist" do
|
||||
@rule.associations_hash.should == {}
|
||||
end
|
||||
|
||||
it "should return no association for joins if just attributes" do
|
||||
it "returns no association for joins if just attributes" do
|
||||
@conditions[:foo] = :bar
|
||||
@rule.associations_hash.should == {}
|
||||
end
|
||||
|
||||
it "should return single association for joins" do
|
||||
it "returns single association for joins" do
|
||||
@conditions[:foo] = {:bar => 1}
|
||||
@rule.associations_hash.should == {:foo => {}}
|
||||
end
|
||||
|
||||
it "should return multiple associations for joins" do
|
||||
it "returns multiple associations for joins" do
|
||||
@conditions[:foo] = {:bar => 1}
|
||||
@conditions[:test] = {1 => 2}
|
||||
@rule.associations_hash.should == {:foo => {}, :test => {}}
|
||||
end
|
||||
|
||||
it "should return nested associations for joins" do
|
||||
it "returns nested associations for joins" do
|
||||
@conditions[:foo] = {:bar => {1 => 2}}
|
||||
@rule.associations_hash.should == {:foo => {:bar => {}}}
|
||||
end
|
||||
|
||||
it "should return no association joins if conditions is nil" do
|
||||
rule = CanCan::Rule.new(true, :read, Integer, nil, nil)
|
||||
it "returns no association joins if conditions is nil" do
|
||||
rule = CanCan::Rule.new(true, :read, :integers)
|
||||
rule.associations_hash.should == {}
|
||||
end
|
||||
|
||||
it "has higher specificity for attributes/conditions" do
|
||||
CanCan::Rule.new(true, :read, :integers).specificity.should eq(1)
|
||||
CanCan::Rule.new(true, :read, :integers, :foo => :bar).specificity.should eq(2)
|
||||
CanCan::Rule.new(true, :read, :integers, :foo).specificity.should eq(2)
|
||||
CanCan::Rule.new(false, :read, :integers).specificity.should eq(3)
|
||||
CanCan::Rule.new(false, :read, :integers, :foo => :bar).specificity.should eq(4)
|
||||
CanCan::Rule.new(false, :read, :integers, :foo).specificity.should eq(4)
|
||||
end
|
||||
|
||||
it "should not be mergeable if conditions are not simple hashes" do
|
||||
meta_where = OpenStruct.new(:name => 'metawhere', :column => 'test')
|
||||
@conditions[meta_where] = :bar
|
||||
|
||||
@rule.should be_unmergeable
|
||||
end
|
||||
|
||||
it "should be mergeable if conditions is an empty hash" do
|
||||
@conditions = {}
|
||||
@rule.should_not be_unmergeable
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
--color
|
||||
--backtrace
|
|
@ -1,35 +1,27 @@
|
|||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
|
||||
Bundler.require(:default)
|
||||
require "sqlite3"
|
||||
require "active_record"
|
||||
|
||||
case ENV["MODEL_ADAPTER"]
|
||||
when "data_mapper"
|
||||
require "dm-core"
|
||||
require "dm-sqlite-adapter"
|
||||
require "dm-migrations"
|
||||
when "mongoid"
|
||||
require "mongoid"
|
||||
end
|
||||
|
||||
require 'supermodel' # shouldn't Bundler do this already?
|
||||
require 'active_support/all'
|
||||
require 'matchers'
|
||||
require 'cancan'
|
||||
require 'cancan/matchers'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.treat_symbols_as_metadata_keys_with_true_values = true
|
||||
config.filter_run :focus => true
|
||||
config.run_all_when_everything_filtered = true
|
||||
config.mock_with :rr
|
||||
config.before(:each) do
|
||||
Project.delete_all
|
||||
Category.delete_all
|
||||
end
|
||||
config.extend WithModel if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
end
|
||||
|
||||
# Working around CVE-2012-5664 requires us to convert all ID params
|
||||
# to strings. Let's switch to using string IDs in tests, otherwise
|
||||
# SuperModel and/or RR will fail (as strings are not fixnums).
|
||||
|
||||
module SuperModel
|
||||
class Base
|
||||
def generate_id
|
||||
object_id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Ability
|
||||
|
@ -39,34 +31,19 @@ class Ability
|
|||
end
|
||||
end
|
||||
|
||||
class Category < SuperModel::Base
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
||||
|
||||
class Category < ActiveRecord::Base
|
||||
connection.create_table(table_name) do |t|
|
||||
t.boolean :visible
|
||||
end
|
||||
has_many :projects
|
||||
end
|
||||
|
||||
module Sub
|
||||
class Project < SuperModel::Base
|
||||
class Project < ActiveRecord::Base
|
||||
connection.create_table(table_name) do |t|
|
||||
t.integer :category_id
|
||||
t.string :name
|
||||
end
|
||||
belongs_to :category
|
||||
attr_accessor :category # why doesn't SuperModel do this automatically?
|
||||
|
||||
def self.respond_to?(method, include_private = false)
|
||||
if method.to_s == "find_by_name!" # hack to simulate ActiveRecord
|
||||
true
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Project < SuperModel::Base
|
||||
belongs_to :category
|
||||
attr_accessor :category # why doesn't SuperModel do this automatically?
|
||||
|
||||
def self.respond_to?(method, include_private = false)
|
||||
if method.to_s == "find_by_name!" # hack to simulate ActiveRecord
|
||||
true
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user