30 Commits
2.0 ... 1.6.10

Author SHA1 Message Date
Ryan Bates
f2f40c7aac releasing 1.6.10 2013-05-07 11:29:15 -07:00
Ryan Bates
ea2b07f416 fix matches_conditons_hash for string values on 1.8 2013-05-07 11:23:08 -07:00
Ryan Bates
60cf6a67ef Merge pull request #675 from bukalapak/master
porting #668 to master branch
2013-05-03 16:15:31 -07:00
Vasiliy Ermolovich
ff2b632f15 Merge pull request #728 from zephyr-dev/patch-1
Add docs for id_param option of load_resource
2013-02-21 21:49:22 -08:00
Ryan Bates
ba82241c0a Merge pull request #705 from albertobajo/mongoid_find_by
Fixed load_resource "find_by" in mongoid resources
2013-02-21 17:50:03 -08:00
Ryan Bates
cbd352c799 Merge pull request #806 from yuszuv/support_for_nested_joins
added support for nested join conditions
2013-02-21 17:45:54 -08:00
Ryan Bates
1cb33bdac5 Merge pull request #800 from wopata/master
Working around a SQL Injection Vulnerability in Ruby on Rails (CVE-2012-5664)
2013-02-21 17:43:36 -08:00
Ben Moss
9f7f520fa7 Add docs for id_param option of load_resource
Added in https://github.com/ryanb/cancan/pull/425 but lacked documentation
2013-02-20 10:05:15 -05:00
Ryan Bates
38d4654523 releasing 1.6.9 2013-02-04 14:18:38 -08:00
jan
e3ba6688b5 added support for nested join conditions 2013-01-20 17:24:17 +01:00
Roland Venesz
d3a8929111 Creating a Project here is unnecessary 2013-01-03 13:26:24 +01:00
Roland Venesz
d5123e0eb3 Working around CVE-2012-5664 2013-01-03 13:16:30 +01:00
Ryan Bates
3f4ee12025 add gem version badge (thanks Gemfury) 2012-12-11 16:58:20 -08:00
Ryan Bates
4dcd544594 Merge pull request #765 from jonsgreen/issue/cancan_inserting_and_null_687
Issue #687: cancan inserting "AND (NULL)" at the end of sql
2012-10-25 14:25:46 -07:00
jonathangreenberg
f5b3fcd8db Issue #687: cancan inserting "AND (NULL)" at the end of sql
Ensure that empty conditions does not trigger unmergeable conditions
2012-10-24 05:36:41 -04:00
Vasiliy Ermolovich
3b50fedd5d Merge pull request #708 from calebthompson/patch-1
Refold generated Ability comments at 80 characters
2012-10-15 11:09:33 -07:00
Vasiliy Ermolovich
d4be93bc83 add contributing guide
[ci skip]
2012-10-13 21:01:46 +03:00
Vasiliy Ermolovich
9a84277549 show build status fro master branch 2012-10-13 20:57:48 +03:00
Caleb Thompson
857dd075c5 Refold generated Ability comments at 80 characters
A lot of people still fold their code at 80 characters, so it is
nice to have generated code consider this.
2012-08-01 17:44:21 -03:00
Alberto Bajo
4a5700c07e Fixed load_resource "find_by" in mongoid resources
Latest versions of Mongoid supports "find_by" query, but syntax
is slightly different than Active Record.
2012-07-30 22:58:17 +02:00
Ryan Bates
b4285ae43c Merge pull request #676 from brynary/master
Add Code Climate badge
2012-07-03 10:48:55 -07:00
Ryan Bates
2db73e60c6 Merge pull request #670 from andhapp/fix-issue-664
Namespaced Controllers not building new resource from params(regression 1.6.8)
2012-07-02 13:47:15 -07:00
Ryan Bates
cad4db2d7b Merge pull request #660 from fl00r/master
Segmentation fault on aliasing
2012-07-02 13:36:44 -07:00
Ryan Bates
d20d90d2c2 Merge pull request #655 from DavidMikeSimon/master
Fix for issue #560
2012-07-02 13:27:21 -07:00
Bryan Helmkamp
8b993ee27d Add Code Climate badge 2012-07-02 14:32:04 -03:00
Nugroho Herucahyono
ce7d3fecdb porting #668 to 1.6.x 2012-07-02 13:05:19 +07:00
Anuj Dutta
60bc9e98a7 Add code for fixing issue #664 (regression in 1.6.8). 2012-06-29 18:53:16 +01:00
fl00r
925274d29a Fixing Segmentation fault on aliasing 2012-06-23 18:25:13 +04:00
David Mike Simon
b162871c6d Spec to test against nested joins being thrown away ala issue 560 2012-06-19 16:58:15 -07:00
David Mike Simon
cfc355c006 Fix for issue 560 where joins could be thrown away by ActiveRecordAdapter::merge_joins 2012-06-18 19:40:48 -07:00
34 changed files with 1432 additions and 1518 deletions

View File

@@ -1 +1 @@
1.9.3-p194 1.8.7-p357

1
.rspec
View File

@@ -1,2 +1 @@
--color --color
--backtrace

1
.rvmrc Normal file
View File

@@ -0,0 +1 @@
rvm use 1.8.7@cancan --create

View File

@@ -1,8 +1,7 @@
rvm: rvm:
- 1.9.3
- 1.9.2
- 1.8.7 - 1.8.7
- ree - ree
notifications: notifications:
recipients: recipients:
- graf.otodrakula@gmail.com
- ryan@railscasts.com - ryan@railscasts.com

View File

@@ -1,3 +1,26 @@
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) 1.6.8 (June 25, 2012)
* improved support for namespaced controllers and models * improved support for namespaced controllers and models

11
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,11 @@
### 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
View File

@@ -1,3 +1,20 @@
source "http://rubygems.org" 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 gemspec

View File

@@ -1,108 +1,120 @@
= CanCan {<img src="https://secure.travis-ci.org/ryanb/cancan.png" />}[http://travis-ci.org/ryanb/cancan] = 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]
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] Wiki[https://github.com/ryanb/cancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
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]. 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.
== Setup == Installation
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. In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
To install CanCan, add it to your Gemfile and run the `bundle` command. gem "cancan"
gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0" In <b>Rails 2</b>, add this to your environment.rb file.
Next generate an Ability class, this is where your permissions will be defined. 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.
rails g cancan:ability rails g cancan:ability
Add authorization by calling {enable_authorization}[https://github.com/ryanb/cancan/wiki/enable_authorization] in your ApplicationController. In Rails 2.3, just add a new class in `app/models/ability.rb` with the folowing contents:
class ApplicationController < ActionController::Base class Ability
enable_authorization include CanCan::Ability
def initialize(user)
end
end end
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. See {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details.
== Defining Abilities === 2. Check Abilities & Authorization
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: The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
if user <% if can? :update, @article %>
can :access, :all <%= link_to "Edit", edit_article_path(@article) %>
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 %> <% end %>
Here the link will only show up the user can create comments. See {Checking Abilities}[https://github.com/ryanb/cancan/wiki/checking-abilities] for more information
The <tt>authorize!</tt> method in the controller will raise an exception if the user is not able to perform the given action.
== Resource Conditions def show
@article = Article.find(params[:id])
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>. authorize! :read, @article
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 end
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. 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.
class ProjectsController < ApplicationController class ArticlesController < ApplicationController
load_and_authorize_resource load_and_authorize_resource
def edit
# @project already loaded here and authorized def show
# @article is already loaded and authorized
end end
end end
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. See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information.
def index
@projects = @projects.where(:hidden => false) === 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
end end
You can check permissions on instances using the <tt>can?</tt> method. See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
<%= link_to "Edit Project", edit_project_path if can? :update, @project %>
Here it will only show the edit link if the +user_id+ matches.
== Resource Attributes === 4. Lock It Down
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+. If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
can :update, :projects, [:name, :priority] class ApplicationController < ActionController::Base
check_authorization
end
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. 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.
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.
can :update, :products == Wiki Docs
cannot :update, :products, :price, :discontinued => true
You can check permissions on specific attributes to determine what to show in the form. * {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]
<%= 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.

View File

@@ -1,6 +1,6 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "cancan" s.name = "cancan"
s.version = "2.0.0.alpha" s.version = "1.6.10"
s.author = "Ryan Bates" s.author = "Ryan Bates"
s.email = "ryan@railscasts.com" s.email = "ryan@railscasts.com"
s.homepage = "http://github.com/ryanb/cancan" s.homepage = "http://github.com/ryanb/cancan"
@@ -10,16 +10,10 @@ Gem::Specification.new do |s|
s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"] s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"]
s.require_path = "lib" s.require_path = "lib"
s.add_development_dependency "rspec", "~> 2.9.0" s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency "rails", "~> 3.2.6" s.add_development_dependency 'rails', '~> 3.0.9'
s.add_development_dependency "sqlite3", "~> 1.3.5" 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 "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.rubyforge_project = s.name
s.required_rubygems_version = ">= 1.3.4" s.required_rubygems_version = ">= 1.3.4"

View File

@@ -8,7 +8,7 @@ module CanCan
# #
# def initialize(user) # def initialize(user)
# if user.admin? # if user.admin?
# can :access, :all # can :manage, :all
# else # else
# can :read, :all # can :read, :all
# end # end
@@ -22,12 +22,21 @@ module CanCan
# #
# You can also pass the class instead of an instance (if you don't have one handy). # You can also pass the class instead of an instance (if you don't have one handy).
# #
# can? :create, :projects # can? :create, Project
# #
# Nested resources can be passed through a hash, this way conditions which are # Nested resources can be passed through a hash, this way conditions which are
# dependent upon the association will work when using a class. # dependent upon the association will work when using a class.
# #
# can? :create, @category => :projects # 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
# #
# Not only can you use the can? method in the controller and view (see ControllerAdditions), # 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. # but you can also call it directly on an ability instance.
@@ -44,9 +53,9 @@ module CanCan
# end # end
# #
# Also see the RSpec Matchers to aid in testing. # Also see the RSpec Matchers to aid in testing.
def can?(action, subject, attribute = nil) def can?(action, subject, *extra_args)
match = relevant_rules_for_match(action, subject, attribute).detect do |rule| match = relevant_rules_for_match(action, subject).detect do |rule|
rule.matches_conditions?(action, subject, attribute) rule.matches_conditions?(action, subject, extra_args)
end end
match ? match.base_behavior : false match ? match.base_behavior : false
end end
@@ -62,22 +71,22 @@ module CanCan
# Defines which abilities are allowed using two arguments. The first one is the action # 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. # you're setting the permission for, the second one is the class of object you're setting it on.
# #
# can :update, :articles # can :update, Article
# #
# You can pass an array for either of these parameters to match any one. # 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. # Here the user has the ability to update or destroy both articles and comments.
# #
# can [:update, :destroy], [:articles, :comments] # can [:update, :destroy], [Article, Comment]
# #
# You can pass :all to match any object and :access to match any action. Here are some examples. # You can pass :all to match any object and :manage to match any action. Here are some examples.
# #
# can :access, :all # can :manage, :all
# can :update, :all # can :update, :all
# can :access, :projects # can :manage, Project
# #
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns. # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
# #
# can :read, :projects, :active => true, :user_id => user.id # can :read, Project, :active => true, :user_id => user.id
# #
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions # 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. # are also used for initial attributes when building a record in ControllerAdditions#load_resource.
@@ -85,7 +94,7 @@ module CanCan
# If the conditions hash does not give you enough control over defining abilities, you can use a block # 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. # along with any Ruby code you want.
# #
# can :update, :projects do |project| # can :update, Project do |project|
# project.groups.include?(user.group) # project.groups.include?(user.group)
# end # end
# #
@@ -93,21 +102,27 @@ module CanCan
# will be denied access. The downside to using a block is that it cannot be used to generate # will be denied access. The downside to using a block is that it cannot be used to generate
# conditions for database queries. # conditions for database queries.
# #
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol. # 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.
# #
# can :update, :projects, :priority => 3 # can :read, :stats
# can? :update, :projects # => true # 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
# #
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the # 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 # 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. # defined in an external source such as the database.
# #
# can do |action, subject, object| # can do |action, object_class, object|
# # check the database and return true/false # # check the database and return true/false
# end # end
# #
def can(*args, &block) def can(action = nil, subject = nil, conditions = nil, &block)
rules << Rule.new(true, *args, &block) rules << Rule.new(true, action, subject, conditions, block)
end end
# Defines an ability which cannot be done. Accepts the same arguments as "can". # Defines an ability which cannot be done. Accepts the same arguments as "can".
@@ -118,30 +133,35 @@ module CanCan
# A block can be passed just like "can", however if the logic is complex it is recommended # A block can be passed just like "can", however if the logic is complex it is recommended
# to use the "can" method. # to use the "can" method.
# #
# cannot :read, :projects do |product| # cannot :read, Product do |product|
# product.invisible? # product.invisible?
# end # end
# #
def cannot(*args, &block) def cannot(action = nil, subject = nil, conditions = nil, &block)
rules << Rule.new(false, *args, &block) rules << Rule.new(false, action, subject, conditions, block)
end end
# Alias one or more actions into another one. # Alias one or more actions into another one.
# #
# alias_action :update, :destroy, :to => :modify # alias_action :update, :destroy, :to => :modify
# can :modify, :comments # can :modify, Comment
# #
# Then :modify permission will apply to both :update and :destroy requests. # Then :modify permission will apply to both :update and :destroy requests.
# #
# can? :update, :comments # => true # can? :update, Comment # => true
# can? :destroy, :comments # => true # can? :destroy, Comment # => true
# #
# This only works in one direction. Passing the aliased action into the "can?" call # 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. # will not work because aliases are meant to generate more generic actions.
# #
# alias_action :update, :destroy, :to => :modify # alias_action :update, :destroy, :to => :modify
# can :update, :comments # can :update, Comment
# can? :modify, :comments # => false # can? :modify, Comment # => false
#
# Unless that exact alias is used.
#
# can :modify, Comment
# can? :modify, Comment # => true
# #
# The following aliases are added by default for conveniently mapping common controller actions. # The following aliases are added by default for conveniently mapping common controller actions.
# #
@@ -152,61 +172,40 @@ module CanCan
# This way one can use params[:action] in the controller to determine the permission. # This way one can use params[:action] in the controller to determine the permission.
def alias_action(*args) def alias_action(*args)
target = args.pop[:to] target = args.pop[:to]
aliases[:actions][target] ||= [] validate_target(target)
aliases[:actions][target] += args aliased_actions[target] ||= []
aliased_actions[target] += args
end end
# Alias one or more subjects into another one. # User shouldn't specify targets with names of real actions or it will cause Seg fault
# def validate_target(target)
# alias_subject :admins, :moderators, :to => :users raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
# 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 end
# Returns a hash of action and subject aliases. # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
def aliases def aliased_actions
@aliases ||= default_aliases @aliased_actions ||= default_alias_actions
end end
# Removes previously aliased actions or subjects including the defaults. # Removes previously aliased actions including the defaults.
def clear_aliases def clear_aliased_actions
aliases[:actions] = {} @aliased_actions = {}
aliases[:subjects] = {}
end end
def model_adapter(model_class, action) def model_adapter(model_class, action)
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class) adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
adapter_class.new(model_class, relevant_rules_for_query(action, model_class.to_s.underscore.pluralize.to_sym)) adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
end end
# See ControllerAdditions#authorize! for documentation. # See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args) def authorize!(action, subject, *args)
message = nil message = nil
if args.last.kind_of?(Hash) if args.last.kind_of?(Hash) && args.last.has_key?(:message)
message = args.pop[:message] message = args.pop[:message]
end end
attribute = args.first
if cannot?(action, subject, *args) if cannot?(action, subject, *args)
message ||= unauthorized_message(action, subject) message ||= unauthorized_message(action, subject)
raise Unauthorized.new(message, action, subject) raise AccessDenied.new(message, action, subject)
elsif sufficient_attribute_check?(action, subject, attribute) && sufficient_condition_check?(action, subject)
fully_authorized!(action, subject)
end end
subject subject
end end
@@ -214,7 +213,7 @@ module CanCan
def unauthorized_message(action, subject) def unauthorized_message(action, subject)
keys = unauthorized_message_keys(action, subject) keys = unauthorized_message_keys(action, subject)
variables = {:action => action.to_s} variables = {:action => action.to_s}
variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize) variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""])) message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
message.blank? ? nil : message message.blank? ? nil : message
end end
@@ -235,25 +234,6 @@ module CanCan
relevant_rules(action, subject).any?(&:only_raw_sql?) relevant_rules(action, subject).any?(&:only_raw_sql?)
end 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) def merge(ability)
ability.send(:rules).each do |rule| ability.send(:rules).each do |rule|
rules << rule.dup rules << rule.dup
@@ -264,37 +244,29 @@ module CanCan
private private
def unauthorized_message_keys(action, subject) def unauthorized_message_keys(action, subject)
subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.pluralize) subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
[aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject| [subject, :all].map do |try_subject|
[aliases_for(:actions, action.to_sym), :access].flatten.map do |try_action| [aliases_for_action(action), :manage].flatten.map do |try_action|
:"#{try_action}.#{try_subject}" :"#{try_action}.#{try_subject}"
end end
end.flatten end.flatten
end 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. # 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 # This should be called before "matches?" and other checking methods since they
# rely on the actions to be expanded. # rely on the actions to be expanded.
def expand_aliases(type, items) def expand_actions(actions)
items.map do |item| actions.map do |action|
aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
end.flatten end.flatten
end end
# Given an action, it will try to find all of the actions which are aliased to it. # 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_aliases. # This does the opposite kind of lookup as expand_actions.
def aliases_for(type, action) def aliases_for_action(action)
results = [action] results = [action]
aliases[type].each do |aliased_action, actions| aliased_actions.each do |aliased_action, actions|
results += aliases_for(type, aliased_action) if actions.include? action results += aliases_for_action(aliased_action) if actions.include? action
end end
results results
end end
@@ -305,20 +277,15 @@ module CanCan
# Returns an array of Rule instances which match the action and subject # Returns an array of Rule instances which match the action and subject
# This does not take into consideration any hash conditions or block statements # This does not take into consideration any hash conditions or block statements
def relevant_rules(action, subject, attribute = nil) def relevant_rules(action, subject)
specificity = 0 rules.reverse.select do |rule|
rules.reverse.each_with_object([]) do |rule, relevant_rules| rule.expanded_actions = expand_actions(rule.actions)
rule.expanded_actions = expand_aliases(:actions, rule.actions) rule.relevant? action, subject
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
end end
def relevant_rules_for_match(action, subject, attribute) def relevant_rules_for_match(action, subject)
relevant_rules(action, subject, attribute).each do |rule| relevant_rules(action, subject).each do |rule|
if rule.only_raw_sql? 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}" 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 end
@@ -326,22 +293,18 @@ module CanCan
end end
def relevant_rules_for_query(action, subject) def relevant_rules_for_query(action, subject)
relevant_rules(action, subject, nil).each do |rule| relevant_rules(action, subject).each do |rule|
if rule.only_block? 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}" 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 end
end end
def default_aliases def default_alias_actions
{ {
:subjects => {},
:actions => {
:read => [:index, :show], :read => [:index, :show],
:create => [:new], :create => [:new],
:update => [:edit], :update => [:edit],
:destroy => [:delete],
}
} }
end end
end end

View File

@@ -12,7 +12,7 @@ module CanCan
# end # end
# #
def load_and_authorize_resource(*args) def load_and_authorize_resource(*args)
cancan_resource_class.add_before_filter(self, {:load => true, :authorize => true}, *args) cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
end end
# Sets up a before filter which loads the model resource into an instance variable. # Sets up a before filter which loads the model resource into an instance variable.
@@ -96,6 +96,11 @@ module CanCan
# #
# load_resource :find_by => :permalink # will use find_by_permalink!(params[:id]) # 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+] # [:+collection+]
# Specify which actions are resource collection actions in addition to :+index+. This # 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. # is usually not necessary because it will try to guess depending on if the id param is present.
@@ -113,8 +118,7 @@ module CanCan
# Passing +true+ will use prepend_before_filter instead of a normal before_filter. # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
# #
def load_resource(*args) def 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_resource, *args)
cancan_resource_class.add_before_filter(self, {:load => true}, *args)
end end
# Sets up a before filter which authorizes the resource using the instance variable. # Sets up a before filter which authorizes the resource using the instance variable.
@@ -173,8 +177,7 @@ module CanCan
# Passing +true+ will use prepend_before_filter instead of a normal before_filter. # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
# #
def authorize_resource(*args) def 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_resource, *args)
cancan_resource_class.add_before_filter(self, {:authorize => true}, *args)
end end
# Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
@@ -202,7 +205,6 @@ module CanCan
# #
# You can also pass the resource name as the first argument to skip that resource. # You can also pass the resource name as the first argument to skip that resource.
def skip_load_resource(*args) 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! options = args.extract_options!
name = args.first name = args.first
cancan_skipper[:load][name] = options cancan_skipper[:load][name] = options
@@ -219,23 +221,20 @@ module CanCan
# #
# You can also pass the resource name as the first argument to skip that resource. # You can also pass the resource name as the first argument to skip that resource.
def skip_authorize_resource(*args) 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! options = args.extract_options!
name = args.first name = args.first
cancan_skipper[:authorize][name] = options cancan_skipper[:authorize][name] = options
end end
# Add this to a controller to automatically perform authorization on every action. # 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.
# #
# class ApplicationController < ActionController::Base # class ApplicationController < ActionController::Base
# enable_authorization # check_authorization
# end # end
# #
# Internally it does this in a before_filter for every action. # See skip_authorization_check to bypass this check on specific controller actions.
#
# 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: # Options:
# [:+only+] # [:+only+]
@@ -245,29 +244,39 @@ module CanCan
# Does not apply to given actions. # Does not apply to given actions.
# #
# [:+if+] # [:+if+]
# Supply the name of a controller method to be called. The authorization only takes place if this returns true. # Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
# #
# enable_authorization :if => :admin_controller? # check_authorization :if => :admin_controller?
# #
# [:+unless+] # [:+unless+]
# Supply the name of a controller method to be called. The authorization only takes place if this returns false. # Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
# #
# enable_authorization :unless => :devise_controller? # check_authorization :unless => :devise_controller?
# #
def enable_authorization(options = {}, &block) def check_authorization(options = {})
before_filter(options.slice(:only, :except)) do |controller| self.after_filter(options.slice(:only, :except)) do |controller|
break if options[:if] && !controller.send(options[:if]) next if controller.instance_variable_defined?(:@_authorized)
break if options[:unless] && controller.send(options[:unless]) next if options[:if] && !controller.send(options[:if])
controller.authorize! controller.params[:action], controller.params[:controller] next if options[:unless] && controller.send(options[:unless])
end raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
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
end end
rescue_from(CanCan::Unauthorized, &block) if block
# 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."
end end
def cancan_resource_class def cancan_resource_class
@@ -278,16 +287,8 @@ module CanCan
end end
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 def cancan_skipper
raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'." @_cancan_skipper ||= {:authorize => {}, :load => {}}
end end
end end
@@ -296,7 +297,7 @@ module CanCan
base.helper_method :can?, :cannot?, :current_ability base.helper_method :can?, :cannot?, :current_ability
end end
# Raises a CanCan::Unauthorized exception if the current_ability cannot # Raises a CanCan::AccessDenied exception if the current_ability cannot
# perform the given action. This is usually called in a controller action or # perform the given action. This is usually called in a controller action or
# before filter to perform the authorization. # before filter to perform the authorization.
# #
@@ -323,12 +324,12 @@ module CanCan
# access is displayed to the user. # access is displayed to the user.
# #
# class ApplicationController < ActionController::Base # class ApplicationController < ActionController::Base
# rescue_from CanCan::Unauthorized do |exception| # rescue_from CanCan::AccessDenied do |exception|
# redirect_to root_url, :alert => exception.message # redirect_to root_url, :alert => exception.message
# end # end
# end # end
# #
# See the CanCan::Unauthorized exception for more details on working with the exception. # See the CanCan::AccessDenied exception for more details on working with the exception.
# #
# See the load_and_authorize_resource method to automatically add the authorize! behavior # See the load_and_authorize_resource method to automatically add the authorize! behavior
# to the default RESTful actions. # to the default RESTful actions.
@@ -337,6 +338,10 @@ module CanCan
current_ability.authorize!(*args) current_ability.authorize!(*args)
end 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 # 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. # want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior. # Just define the method in the controller to change behavior.

View File

@@ -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. # 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. # This class is used internally, so you do not need to call methods directly on it.
class ControllerResource # :nodoc: class ControllerResource # :nodoc:
def self.add_before_filter(controller_class, behavior, *args) def self.add_before_filter(controller_class, method, *args)
options = args.extract_options!.merge(behavior) options = args.extract_options!
resource_name = args.first resource_name = args.first
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter 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.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)).process controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
end end
end end
@@ -16,27 +16,29 @@ module CanCan
@params = controller.params @params = controller.params
@options = args.extract_options! @options = args.extract_options!
@name = args.first @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 end
def process def load_and_authorize_resource
if @options[:load] load_resource
authorize_resource
end
def load_resource
unless skip?(:load)
if load_instance? if load_instance?
self.resource_instance ||= load_resource_instance self.resource_instance ||= load_resource_instance
elsif load_collection? elsif load_collection?
self.collection_instance ||= load_collection self.collection_instance ||= load_collection
current_ability.fully_authorized! @params[:action], @params[:controller]
end end
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 end
def authorize_resource
unless skip?(:authorize)
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
end end
end end
@@ -44,18 +46,18 @@ module CanCan
@options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
end end
# def skip?(behavior) # This could probably use some refactoring def skip?(behavior) # This could probably use some refactoring
# options = @controller.class.cancan_skipper[behavior][@name] options = @controller.class.cancan_skipper[behavior][@name]
# if options.nil? if options.nil?
# false false
# elsif options == {} elsif options == {}
# true true
# elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym) elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
# true true
# elsif [options[:only]].flatten.include?(@params[:action].to_sym) elsif [options[:only]].flatten.include?(@params[:action].to_sym)
# true true
# end end
# end end
protected protected
@@ -63,7 +65,7 @@ module CanCan
if !parent? && new_actions.include?(@params[:action].to_sym) if !parent? && new_actions.include?(@params[:action].to_sym)
build_resource build_resource
elsif id_param || @options[:singleton] elsif id_param || @options[:singleton]
find_and_update_resource find_resource
end end
end end
@@ -72,7 +74,7 @@ module CanCan
end end
def load_collection? def load_collection?
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, subject_name) resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
end end
def load_collection def load_collection
@@ -85,29 +87,19 @@ module CanCan
end end
def assign_attributes(resource) def assign_attributes(resource)
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
initial_attributes.each do |attr_name, value| initial_attributes.each do |attr_name, value|
resource.send("#{attr_name}=", value) resource.send("#{attr_name}=", value)
end end
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
resource resource
end end
def initial_attributes def initial_attributes
current_ability.attributes_for(@params[:action].to_sym, subject_name).delete_if do |key, value| current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
resource_params && resource_params.include?(key) resource_params && resource_params.include?(key)
end end
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 def find_resource
if @options[:singleton] && parent_resource.respond_to?(name) if @options[:singleton] && parent_resource.respond_to?(name)
parent_resource.send(name) parent_resource.send(name)
@@ -115,6 +107,8 @@ module CanCan
if @options[:find_by] if @options[:find_by]
if resource_base.respond_to? "find_by_#{@options[:find_by]}!" if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
resource_base.send("find_by_#{@options[:find_by]}!", id_param) 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 else
resource_base.send(@options[:find_by], id_param) resource_base.send(@options[:find_by], id_param)
end end
@@ -137,7 +131,7 @@ module CanCan
@params[@options[:id_param]] @params[@options[:id_param]]
else else
@params[parent? ? :"#{name}_id" : :id] @params[parent? ? :"#{name}_id" : :id]
end end.to_s
end end
def member_action? def member_action?
@@ -156,12 +150,8 @@ module CanCan
end end
end end
def subject_name def resource_class_with_parent
resource_class.to_s.underscore.pluralize.to_sym parent_resource ? {parent_resource => resource_class} : resource_class
end
def subject_name_with_parent
parent_resource ? {parent_resource => subject_name} : subject_name
end end
def resource_instance=(instance) def resource_instance=(instance)
@@ -169,13 +159,7 @@ module CanCan
end end
def resource_instance def resource_instance
if load_instance? @controller.instance_variable_get("@#{instance_name}") 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 end
def collection_instance=(instance) def collection_instance=(instance)
@@ -197,7 +181,7 @@ module CanCan
elsif @options[:shallow] elsif @options[:shallow]
resource_class resource_class
else else
raise Unauthorized.new(nil, authorization_action, @params[:controller].to_sym) # maybe this should be a record not found error instead? raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
end end
else else
resource_class resource_class
@@ -231,10 +215,15 @@ module CanCan
def resource_params def resource_params
if @options[:class] if @options[:class]
@params[@options[:class].to_s.underscore.gsub('/', '_')] params_key = extract_key(@options[:class])
else return @params[params_key] if @params[params_key]
@params[namespaced_name.to_s.underscore.gsub("/", "_")]
end end
resource_params_by_namespaced_name
end
def resource_params_by_namespaced_name
@params[extract_key(namespaced_name)]
end end
def namespace def namespace
@@ -262,5 +251,11 @@ module CanCan
def new_actions def new_actions
[:new, :create] + [@options[:new]].flatten [:new, :create] + [@options[:new]].flatten
end end
private
def extract_key(value)
value.to_s.underscore.gsub('/', '_')
end
end end
end end

View File

@@ -11,14 +11,11 @@ module CanCan
# Raised when using check_authorization without calling authorized! # Raised when using check_authorization without calling authorized!
class AuthorizationNotPerformed < Error; end 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 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 # This usually happens within a call to ControllerAdditions#authorize! but can be
# raised manually. # raised manually.
# #
# raise CanCan::Unauthorized.new("Not authorized!", :read, Article) # raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
# #
# The passed message, action, and subject are optional and can later be retrieved when # The passed message, action, and subject are optional and can later be retrieved when
# rescuing from the exception. # rescuing from the exception.
@@ -33,9 +30,9 @@ module CanCan
# exception.default_message = "Default error message" # exception.default_message = "Default error message"
# exception.message # => "Default error message" # exception.message # => "Default error message"
# #
# See ControllerAdditions#authorize! for more information on rescuing from this exception # See ControllerAdditions#authorized! for more information on rescuing from this exception
# and customizing the message using I18n. # and customizing the message using I18n.
class Unauthorized < Error class AccessDenied < Error
attr_reader :action, :subject attr_reader :action, :subject
attr_writer :default_message attr_writer :default_message

View File

@@ -66,11 +66,22 @@ module CanCan
return conditions unless conditions.kind_of? Hash return conditions unless conditions.kind_of? Hash
conditions.inject({}) do |result_hash, (name, value)| conditions.inject({}) do |result_hash, (name, value)|
if value.kind_of? Hash if value.kind_of? Hash
value = value.dup
association_class = model_class.reflect_on_association(name).class_name.constantize 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 name = model_class.reflect_on_association(name).table_name.to_sym
value = tableized_conditions(value, association_class)
end
result_hash[name] = value result_hash[name] = value
end
nested
end
result_hash.merge!(tableized_conditions(nested,association_class))
else
result_hash[name] = value
end
result_hash result_hash
end end
end end
@@ -145,8 +156,8 @@ module CanCan
# Takes two hashes and does a deep merge. # Takes two hashes and does a deep merge.
def merge_joins(base, add) def merge_joins(base, add)
add.each do |name, nested| add.each do |name, nested|
if base[name].is_a?(Hash) && !nested.empty? if base[name].is_a?(Hash)
merge_joins(base[name], nested) merge_joins(base[name], nested) unless nested.empty?
else else
base[name] = nested base[name] = nested
end end
@@ -165,8 +176,6 @@ module CanCan
end end
end end
ActiveSupport.on_load(:active_record) do ActiveRecord::Base.class_eval do
ActiveRecord::Base.class_eval do
include CanCan::ModelAdditions include CanCan::ModelAdditions
end
end end

View File

@@ -2,8 +2,6 @@ module CanCan
# This module adds the accessible_by class method to a model. It is included in the model adapters. # This module adds the accessible_by class method to a model. It is included in the model adapters.
module ModelAdditions module ModelAdditions
extend ActiveSupport::Concern
module ClassMethods module ClassMethods
# Returns a scope which fetches only the records that the passed ability # Returns a scope which fetches only the records that the passed ability
# can perform a given action on. The action defaults to :index. This # can perform a given action on. The action defaults to :index. This
@@ -25,5 +23,9 @@ module CanCan
ability.model_adapter(self, action).database_records ability.model_adapter(self, action).database_records
end end
end end
def self.included(base)
base.extend ClassMethods
end
end end
end end

View File

@@ -4,38 +4,37 @@ module CanCan
# helpful methods to determine permission checking and conditions hash generation. # helpful methods to determine permission checking and conditions hash generation.
class Rule # :nodoc: class Rule # :nodoc:
attr_reader :base_behavior, :subjects, :actions, :conditions attr_reader :base_behavior, :subjects, :actions, :conditions
attr_writer :expanded_actions, :expanded_subjects attr_writer :expanded_actions
# The first argument when initializing is the base_behavior which is a true/false # 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 # 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 # 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. # of conditions and the last one is the block passed to the "can" call.
def initialize(base_behavior, action = nil, subject = nil, *extra_args, &block) 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?
@match_all = action.nil? && subject.nil? @match_all = action.nil? && subject.nil?
@base_behavior = base_behavior @base_behavior = base_behavior
@actions = [action].flatten @actions = [action].flatten
@subjects = [subject].flatten @subjects = [subject].flatten
@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) @conditions = conditions || {}
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 @block = block
end end
# Matches the subject, action, and given attribute. Conditions are not checked here. # Matches both the subject and action, not necessarily the conditions
def relevant?(action, subject, attribute) def relevant?(action, subject)
subject = subject.values.first if subject.class == Hash subject = subject.values.first if subject.class == Hash
@match_all || (matches_action?(action) && matches_subject?(subject) && matches_attribute?(attribute)) @match_all || (matches_action?(action) && matches_subject?(subject))
end end
# Matches the block or conditions hash # Matches the block or conditions hash
def matches_conditions?(action, subject, attribute) def matches_conditions?(action, subject, extra_args)
if @match_all if @match_all
call_block_with_all(action, subject, attribute) call_block_with_all(action, subject, extra_args)
elsif @block && subject_object?(subject) elsif @block && !subject_class?(subject)
@block.arity == 1 ? @block.call(subject) : @block.call(subject, attribute) @block.call(subject, *extra_args)
elsif @conditions.kind_of?(Hash) && subject.class == Hash elsif @conditions.kind_of?(Hash) && subject.class == Hash
nested_subject_matches_conditions?(subject) nested_subject_matches_conditions?(subject)
elsif @conditions.kind_of?(Hash) && subject_object?(subject) elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
matches_conditions_hash?(subject) matches_conditions_hash?(subject)
else else
# Don't stop at "cannot" definitions when there are conditions. # Don't stop at "cannot" definitions when there are conditions.
@@ -44,27 +43,20 @@ module CanCan
end end
def only_block? def only_block?
!conditions? && !@block.nil? conditions_empty? && !@block.nil?
end end
def only_raw_sql? def only_raw_sql?
@block.nil? && conditions? && !@conditions.kind_of?(Hash) @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
end end
def attributes? def conditions_empty?
@attributes.present? @conditions == {} || @conditions.nil?
end
def conditions?
@conditions.present?
end
def instance_conditions?
@block || conditions?
end end
def unmergeable? def unmergeable?
@conditions.respond_to?(:keys) && (! @conditions.keys.first.kind_of? Symbol) @conditions.respond_to?(:keys) && @conditions.present? &&
(!@conditions.keys.first.kind_of? Symbol)
end end
def associations_hash(conditions = @conditions) def associations_hash(conditions = @conditions)
@@ -83,42 +75,23 @@ module CanCan
attributes attributes
end end
def specificity
specificity = 1
specificity += 1 if attributes? || conditions?
specificity += 2 unless base_behavior
specificity
end
private private
def subject_object?(subject) def subject_class?(subject)
# klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
# klass == Class || klass == Module klass == Class || klass == Module
!subject.kind_of?(Symbol) && !subject.kind_of?(String)
end end
def matches_action?(action) def matches_action?(action)
@expanded_actions.include?(:access) || @expanded_actions.include?(action.to_sym) @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
end end
def matches_subject?(subject) def matches_subject?(subject)
subject = subject_name(subject) if subject_object? subject @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
@expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
end 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) def matches_subject_class?(subject)
@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)) } @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 end
# Checks if the given subject matches the given conditions hash. # Checks if the given subject matches the given conditions hash.
@@ -141,9 +114,9 @@ module CanCan
if attribute.kind_of? Array if attribute.kind_of? Array
attribute.any? { |element| matches_conditions_hash? element, value } attribute.any? { |element| matches_conditions_hash? element, value }
else else
attribute && matches_conditions_hash?(attribute, value) !attribute.nil? && matches_conditions_hash?(attribute, value)
end end
elsif value.kind_of?(Enumerable) elsif !value.is_a?(String) && value.kind_of?(Enumerable)
value.include? attribute value.include? attribute
else else
attribute == value attribute == value
@@ -159,20 +132,16 @@ module CanCan
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {}) matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
end end
def call_block_with_all(action, subject, attribute) def call_block_with_all(action, subject, extra_args)
if subject_object? subject if subject.class == Class
@block.call(action, subject_name(subject), subject, attribute) @block.call(action, subject, nil, *extra_args)
else else
@block.call(action, subject, nil, attribute) @block.call(action, subject.class, subject, *extra_args)
end end
end end
def subject_name(subject)
subject.class.to_s.underscore.pluralize.to_sym
end
def model_adapter(subject) def model_adapter(subject)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject) CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
end end
end end
end end

View File

@@ -1,5 +1,4 @@
Description: Description:
The cancan:ability generator creates an Ability class in the models The cancan:ability generator creates an Ability class in the models
directory. You can move this file anywhere you want as long as it directory. You can move this file anywhere you want as long as it
is in the load path. A test/spec file is also generated depending is in the load path.
on if a spec directory exists.

View File

@@ -1,15 +1,10 @@
module Cancan module Cancan
module Generators module Generators
class AbilityGenerator < Rails::Generators::Base class AbilityGenerator < Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__) source_root File.expand_path('../templates', __FILE__)
def generate_ability def generate_ability
copy_file "ability.rb", "app/models/ability.rb" 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 end
end end

View File

@@ -2,23 +2,31 @@ class Ability
include CanCan::Ability include CanCan::Ability
def initialize(user) def initialize(user)
# Define abilities for the passed in (current) user. For example: # Define abilities for the passed in user here. For example:
# #
# if user # user ||= User.new # guest user (not logged in)
# can :access, :all # if user.admin?
# can :manage, :all
# else # else
# can :access, :home # can :read, :all
# can :create, [:users, :sessions]
# end # end
# #
# Here if there is a user he will be able to perform any action on any controller. # The first argument to `can` is the action you are giving the user
# If someone is not logged in he can only access the home, users, and sessions controllers. # permission to do.
# If you pass :manage it will apply to every action. Other common actions
# here are :read, :create, :update and :destroy.
# #
# The first argument to `can` is the action the user can perform. The second argument # The second argument is the resource the user can perform the action on.
# is the controller name they can perform that action on. You can pass :access and :all # If you pass :all it will apply to every resource. Otherwise pass a Ruby
# to represent any action and controller respectively. Passing an array to either of # class of the resource.
# these will grant permission on each item in the array.
# #
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities # 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
end end
end end

View File

@@ -1,16 +0,0 @@
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

View File

@@ -1,10 +0,0 @@
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

View File

@@ -6,421 +6,358 @@ describe CanCan::Ability do
@ability.extend(CanCan::Ability) @ability.extend(CanCan::Ability)
end end
it "should be able to :read anything" do
# Basic Action & Subject @ability.can :read, :all
@ability.can?(:read, String).should be_true
it "allows access to only what is defined" do @ability.can?(:read, 123).should be_true
@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 end
it "allows access to everything when :access, :all is used" do it "should not have permission to do something it doesn't know about" do
@ability.can?(:paint, :fences).should be_false @ability.can?(:foodfight, String).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 end
it "allows access to multiple actions and subjects" do it "should pass true to `can?` when non false/nil is returned in block" do
@ability.can [:paint, :sand], [:fences, :decks] @ability.can :read, :all
@ability.can?(:paint, :fences).should be_true @ability.can :read, Symbol do |sym|
@ability.can?(:sand, :fences).should be_true "foo" # TODO test that sym is nil when no instance is passed
@ability.can?(:paint, :decks).should be_true end
@ability.can?(:sand, :decks).should be_true @ability.can?(:read, :some_symbol).should == true
@ability.can?(:wax, :fences).should be_false
@ability.can?(:paint, :cars).should be_false
end end
it "allows strings instead of symbols in ability check" do it "should pass nil to a block when no instance is passed" do
@ability.can :paint, :fences @ability.can :read, Symbol do |sym|
@ability.can?("paint", "fences").should be_true sym.should be_nil
true
end
@ability.can?(:read, Symbol).should be_true
end end
it "should pass to previous rule, if block returns false or nil" do
# Aliases @ability.can :read, Symbol
@ability.can :read, Integer do |i|
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 "follows deep action aliases" do
@ability.alias_action :update, :destroy, :to => :modify
@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 "adds up action aliases" do
@ability.alias_action :update, :to => :modify
@ability.alias_action :destroy, :to => :modify
@ability.can :modify, :projects
@ability.can?(:update, :projects).should be_true
@ability.can?(:destroy, :projects).should be_true
end
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 "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
# 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, 123).should be_false
end
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 "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 "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 "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 i < 5
end end
@ability.can :read, :fixnums do |i| @ability.can :read, Integer do |i|
i > 10 i > 10
end end
@ability.can?(:read, Symbol).should be_true
@ability.can?(:read, 11).should be_true @ability.can?(:read, 11).should be_true
@ability.can?(:read, 1).should be_true @ability.can?(:read, 1).should be_true
@ability.can?(:read, 6).should be_false @ability.can?(:read, 6).should be_false
end end
it "calls block passing arguments when no arguments are given to can" do it "should not pass class with object if :all objects are accepted" do
@ability.can do |action, subject, object| @ability.can :preview, :all do |object|
action.should == :read object.should == 123
subject.should == :ranges
object.should == (2..4)
@block_called = true @block_called = true
end end
@ability.can?(:read, 2..4) @ability.can?(:preview, 123)
@block_called.should be_true @block_called.should be_true
end end
it "raises an error when attempting to use a block with a hash condition since it's not likely what they want" do it "should not call block when only class is passed, only return true" do
lambda { @block_called = false
@ability.can :read, :ranges, :published => true do @ability.can :preview, :all do |object|
false @block_called = true
end 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.") @ability.can?(:preview, Hash).should be_true
@block_called.should be_false
end end
it "does not raise an error when attempting to use a block with an array of SQL conditions" do it "should pass only object for global manage actions" do
lambda { @ability.can :manage, String do |object|
@ability.can :read, :ranges, ["published = ?", true] do object.should == "foo"
false @block_called = true
end end
}.should_not raise_error(CanCan::Error) @ability.can?(:stuff, "foo").should
@block_called.should be_true
end end
it "should alias update or destroy actions to modify action" do
# Attributes @ability.alias_action :update, :destroy, :to => :modify
@ability.can :modify, :all
it "allows permission on attributes" do @ability.can?(:update, 123).should be_true
@ability.can :update, :users, :name @ability.can?(:destroy, 123).should be_true
@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 end
it "allows permission on all attributes when none are given" do it "should allow deeply nested aliased actions" do
@ability.can :update, :users @ability.alias_action :increment, :to => :sort
@ability.can?(:update, :users, :password).should be_true @ability.alias_action :sort, :to => :modify
@ability.can :modify, :all
@ability.can?(:increment, 123).should be_true
end end
it "allows strings when chekcing attributes" do it "should raise an Error if alias target is an exist action" do
@ability.can :update, :users, :name 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")
@ability.can?(:update, :users, "name").should be_true
end end
it "combines attribute check with conditions hash" do it "should always call block with arguments when passing no arguments to can" do
@ability.can :update, :ranges, :begin => 1 @ability.can do |action, object_class, object|
@ability.can :update, :ranges, :name, :begin => 2 action.should == :foo
@ability.can?(:update, 1..3, :foobar).should be_true object_class.should == 123.class
@ability.can?(:update, 2..4, :foobar).should be_false object.should == 123
@ability.can?(:update, 2..4, :name).should be_true @block_called = true
@ability.can?(:update, 3..5, :name).should be_false end
@ability.can?(:foo, 123)
@block_called.should be_true
end end
it "passes attribute to block and nil if no attribute checked" do it "should pass nil to object when comparing class with can check" do
@ability.can :update, :ranges do |range, attribute| @ability.can do |action, object_class, object|
attribute == :name action.should == :foo
object_class.should == Hash
object.should be_nil
@block_called = true
end end
@ability.can?(:update, 1..3, :name).should be_true @ability.can?(:foo, Hash)
@ability.can?(:update, 2..4).should be_false @block_called.should be_true
end end
it "passes attribute to block for global can definition" do it "should automatically alias index and show into read calls" do
@ability.can do |action, subject, object, attribute| @ability.can :read, :all
attribute == :name @ability.can?(:index, 123).should be_true
end @ability.can?(:show, 123).should be_true
@ability.can?(:update, 1..3, :name).should be_true
@ability.can?(:update, 2..4).should be_false
end end
it "should automatically alias new and edit into create and update respectively" do
# Checking if Fully Authorized @ability.can :create, :all
@ability.can :update, :all
it "is not fully authorized when no authorize! call is made" do @ability.can?(:new, 123).should be_true
@ability.can :update, :ranges, :begin => 1 @ability.can?(:edit, 123).should be_true
@ability.can?(:update, :ranges).should be_true
@ability.should_not be_fully_authorized(:update, :ranges)
end end
it "is fully authorized when calling authorize! with a matching action and subject" do it "should not respond to prepare (now using initialize)" do
@ability.can :update, :ranges @ability.should_not respond_to(:prepare)
@ability.authorize! :update, :ranges
@ability.should be_fully_authorized(:update, :ranges)
@ability.should_not be_fully_authorized(:create, :ranges)
end end
it "is fully authorized when marking action and subject as such" do it "should offer cannot? method which is simply invert of can?" do
@ability.fully_authorized! :update, :ranges @ability.cannot?(:tie, String).should be_true
@ability.should be_fully_authorized(:update, :ranges)
end end
it "is not fully authorized when a conditions hash exists but no instance is used" do it "should be able to specify multiple actions and match any" do
@ability.can :update, :ranges, :begin => 1 @ability.can [:read, :update], :all
@ability.authorize! :update, :ranges @ability.can?(:read, 123).should be_true
@ability.should_not be_fully_authorized(:update, :ranges) @ability.can?(:update, 123).should be_true
@ability.authorize! "update", "ranges" @ability.can?(:count, 123).should be_false
@ability.should_not be_fully_authorized(:update, :ranges)
@ability.authorize! :update, 1..3
@ability.should be_fully_authorized(:update, :ranges)
end end
it "is not fully authorized when a block exists but no instance is used" do it "should be able to specify multiple classes and match any" do
@ability.can :update, :ranges do |range| @ability.can :update, [String, Range]
range.begin == 1 @ability.can?(:update, "foo").should be_true
@ability.can?(:update, 1..3).should be_true
@ability.can?(:update, 123).should be_false
end end
@ability.authorize! :update, :ranges
@ability.should_not be_fully_authorized(:update, :ranges) it "should support custom objects in the rule" do
@ability.authorize! :update, 1..3 @ability.can :read, :stats
@ability.should be_fully_authorized(:update, :ranges) @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
@ability.alias_action :update, :to => :modify
@ability.alias_action :destroy, :to => :modify
@ability.aliased_actions[:modify].should == [:update, :destroy]
end
it "should clear aliased actions" do
@ability.alias_action :update, :to => :modify
@ability.clear_aliased_actions
@ability.aliased_actions[:modify].should be_nil
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
end
it "should use conditions as third parameter and determine abilities from it" do
@ability.can :read, Range, :begin => 1, :end => 3
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 1..4).should be_false
@ability.can?(:read, Range).should be_true
end
it "should allow an array of options in conditions hash" do
@ability.can :read, Range, :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
@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 }
@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 }
@ability.can?(:read, 1..5).should be_true
@ability.can?(:read, 4..6).should be_false
end end
it "should accept a set as a condition value" do it "should accept a set as a condition value" do
object_with_foo_2 = Object.new mock(object_with_foo_2 = Object.new).foo { 2 }
object_with_foo_2.should_receive(:foo) { 2 } mock(object_with_foo_3 = Object.new).foo { 3 }
object_with_foo_3 = Object.new @ability.can :read, Object, :foo => [1, 2, 5].to_set
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_2).should be_true
@ability.can?(:read, object_with_foo_3).should be_false @ability.can?(:read, object_with_foo_3).should be_false
end end
it "does not match subjects return nil for methods that must match nested a nested conditions hash" do it "should not match subjects return nil for methods that must match nested a nested conditions hash" do
object_with_foo = Object.new mock(object_with_foo = Object.new).foo { :bar }
object_with_foo.should_receive(:foo) { :bar } @ability.can :read, Array, :first => { :foo => :bar }
@ability.can :read, :arrays, :first => { :foo => :bar }
@ability.can?(:read, [object_with_foo]).should be_true @ability.can?(:read, [object_with_foo]).should be_true
@ability.can?(:read, []).should be_false @ability.can?(:read, []).should be_false
end end
it "is not fully authorized when attributes are required but not checked in update/create actions" do it "should match strings but not substrings specified in a conditions hash" do
@ability.can :access, :users, :name @ability.can :read, String, :presence => "declassified"
@ability.authorize! :update, :users @ability.can?(:read, "declassified").should be_true
@ability.should_not be_fully_authorized(:update, :users) @ability.can?(:read, "classified").should be_false
@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 end
it "marks as fully authorized when authorizing with strings instead of symbols" do it "should not stop at cannot definition when comparing class" do
@ability.fully_authorized! "update", "ranges" @ability.can :read, Range
@ability.should be_fully_authorized(:update, :ranges) @ability.cannot :read, Range, :begin => 1
@ability.should be_fully_authorized("update", "ranges") @ability.can?(:read, 2..5).should be_true
@ability.can :update, :users @ability.can?(:read, 1..5).should be_false
@ability.authorize! "update", "users" @ability.can?(:read, Range).should be_true
@ability.should be_fully_authorized(:update, :users)
end 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.can :read, :all
@ability.cannot :read, :ranges @ability.cannot :read, Range
@ability.can?(:read, :books).should be_true @ability.can?(:read, 1..5).should be_false
@ability.can?(:read, 1..3).should be_false @ability.can?(:read, Range).should be_false
@ability.can?(:read, :ranges).should be_false
end end
it "passes to previous rule if cannot check returns false" do it "should allow to check ability for Module" do
@ability.can :read, :all module B; end
@ability.cannot :read, :ranges, :begin => 3 class A; include B; end
@ability.cannot :read, :ranges do |range| @ability.can :read, B
range.begin == 5 @ability.can?(:read, A).should be_true
end @ability.can?(:read, A.new).should be_true
@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 end
it "rejects permission only to a given attribute" do it "should pass nil to a block for ability on Module when no instance is passed" do
@ability.can :update, :books module B; end
@ability.cannot :update, :books, :author class A; include B; end
@ability.can?(:update, :books).should be_true @ability.can :read, B do |sym|
@ability.can?(:update, :books, :author).should be_false sym.should be_nil
true
end
@ability.can?(:read, B).should be_true
@ability.can?(:read, A).should be_true
end end
# Hash Association it "passing a hash of subjects should check permissions through association" do
@ability.can :read, Range, :string => {:length => 3}
it "checks permission through association when hash is passed as subject" do @ability.can?(:read, "foo" => Range).should be_true
@ability.can :read, :books, :range => {:begin => 3} @ability.can?(:read, "foobar" => Range).should be_false
@ability.can?(:read, (1..4) => :books).should be_false @ability.can?(:read, 123 => Range).should be_true
@ability.can?(:read, (3..5) => :books).should be_true
@ability.can?(:read, 123 => :books).should be_true
end end
it "checks permissions on association hash with multiple rules" do it "passing a hash of subjects with multiple definitions should check permissions correctly" do
@ability.can :read, :books, :range => {:begin => 3} @ability.can :read, Range, :string => {:length => 4}
@ability.can :read, :books, :range => {:end => 6} @ability.can [:create, :read], Range, :string => {:upcase => 'FOO'}
@ability.can?(:read, (1..4) => :books).should be_false @ability.can?(:read, "foo" => Range).should be_true
@ability.can?(:read, (3..5) => :books).should be_true @ability.can?(:read, "foobar" => Range).should be_false
@ability.can?(:read, (1..6) => :books).should be_true @ability.can?(:read, 1234 => Range).should be_true
@ability.can?(:read, 123 => :books).should be_true
end end
it "checks ability on hash subclass" do it "should allow to check ability on Hash-like object" do
class Container < Hash; end class Container < Hash; end
@ability.can :read, :containers @ability.can :read, Container
@ability.can?(:read, Container.new).should be_true @ability.can?(:read, Container.new).should be_true
end end
it "should have initial attributes based on hash conditions of 'new' action" do
# Initial Attributes @ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
@ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
it "has initial attributes based on hash conditions for a given action" do @ability.can :new, Range, :baz => "baz", :range => 1..3
@ability.can :access, :ranges, :foo => "foo", :hash => {:skip => "hashes"} @ability.cannot :new, Range, :ignore => "me"
@ability.can :create, :ranges, :bar => 123, :array => %w[skip arrays] @ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"}
@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 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 begin
@ability.authorize! :read, :books, :message => "Access denied!" @ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
rescue CanCan::Unauthorized => e rescue CanCan::AccessDenied => e
e.message.should == "Access denied!" e.message.should == "Access denied!"
e.action.should == :read e.action.should == :read
e.subject.should == :books e.subject.should == :foo
else else
fail "Expected CanCan::Unauthorized exception to be raised" fail "Expected CanCan::AccessDenied exception to be raised"
end end
end end
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do it "should not raise access denied exception if ability is authorized to perform an action and return subject" do
@ability.can :read, :foo @ability.can :read, :foo
lambda { lambda {
@ability.authorize!(:read, :foo).should == :foo @ability.authorize!(:read, :foo).should == :foo
}.should_not raise_error }.should_not raise_error
end end
it "knows when block is used in conditions" do it "should know when block is used in conditions" do
@ability.can :read, :foo @ability.can :read, :foo
@ability.should_not have_block(:read, :foo) @ability.should_not have_block(:read, :foo)
@ability.can :read, :foo do |foo| @ability.can :read, :foo do |foo|
@@ -429,106 +366,85 @@ describe CanCan::Ability do
@ability.should have_block(:read, :foo) @ability.should have_block(:read, :foo)
end end
it "knows when raw sql is used in conditions" do it "should know when raw sql is used in conditions" do
@ability.can :read, :foo @ability.can :read, :foo
@ability.should_not have_raw_sql(:read, :foo) @ability.should_not have_raw_sql(:read, :foo)
@ability.can :read, :foo, 'false' @ability.can :read, :foo, 'false'
@ability.should have_raw_sql(:read, :foo) @ability.should have_raw_sql(:read, :foo)
end end
it "raises access denied exception with default message if not specified" do it "should raise access denied exception with default message if not specified" do
begin begin
@ability.authorize! :read, :books @ability.authorize! :read, :foo
rescue CanCan::Unauthorized => e rescue CanCan::AccessDenied => e
e.default_message = "Access denied!" e.default_message = "Access denied!"
e.message.should == "Access denied!" e.message.should == "Access denied!"
else else
fail "Expected CanCan::Unauthorized exception to be raised" fail "Expected CanCan::AccessDenied exception to be raised"
end end
end end
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do it "should determine model adapter class by asking AbstractAdapter" 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 model_class = Object.new
adapter_class = Object.new adapter_class = Object.new
CanCan::ModelAdapters::AbstractAdapter.stub(:adapter_class).with(model_class) { adapter_class } stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
adapter_class.stub(:new).with(model_class, []) { :adapter_instance } stub(adapter_class).new(model_class, []) { :adapter_instance }
@ability.model_adapter(model_class, :read).should == :adapter_instance @ability.model_adapter(model_class, :read).should == :adapter_instance
end 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
# Unauthorized I18n Message 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
describe "unauthorized message" do describe "unauthorized message" do
after(:each) do after(:each) do
I18n.backend = nil I18n.backend = nil
end end
it "uses action/subject in i18n" do it "should use action/subject in i18n" do
I18n.backend.store_translations :en, :unauthorized => {:update => {:ranges => "update ranges"}} I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
@ability.unauthorized_message(:update, :ranges).should == "update ranges" @ability.unauthorized_message(:update, Array).should == "foo"
@ability.unauthorized_message(:update, 2..4).should == "update ranges" @ability.unauthorized_message(:update, [1, 2, 3]).should == "foo"
@ability.unauthorized_message(:update, :missing).should be_nil @ability.unauthorized_message(:update, :missing).should be_nil
end end
it "uses symbol as subject directly" do it "should use symbol as subject directly" do
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}} 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." @ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
end end
it "falls back to 'access' and 'all'" do it "should fall back to 'manage' and 'all'" do
I18n.backend.store_translations :en, :unauthorized => { I18n.backend.store_translations :en, :unauthorized => {
:access => {:all => "access all", :ranges => "access ranges"}, :manage => {:all => "manage all", :array => "manage array"},
:update => {:all => "update all", :ranges => "update ranges"} :update => {:all => "update all", :array => "update array"}
} }
@ability.unauthorized_message(:update, :ranges).should == "update ranges" @ability.unauthorized_message(:update, Array).should == "update array"
@ability.unauthorized_message(:update, :hashes).should == "update all" @ability.unauthorized_message(:update, Hash).should == "update all"
@ability.unauthorized_message(:create, :ranges).should == "access ranges" @ability.unauthorized_message(:foo, Array).should == "manage array"
@ability.unauthorized_message(:create, :hashes).should == "access all" @ability.unauthorized_message(:foo, Hash).should == "manage all"
end end
it "follows aliases" do it "should follow aliased actions" do
I18n.backend.store_translations :en, :unauthorized => {:modify => {:ranges => "modify ranges"}} I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
@ability.alias_action :update, :to => :modify @ability.alias_action :update, :to => :modify
@ability.alias_subject :areas, :to => :ranges @ability.unauthorized_message(:update, Array).should == "modify array"
@ability.unauthorized_message(:update, :areas).should == "modify ranges" @ability.unauthorized_message(:edit, Array).should == "modify array"
@ability.unauthorized_message(:edit, :ranges).should == "modify ranges"
end end
it "has variables for action and subject" do it "should have 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 I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
@ability.unauthorized_message(:update, :ranges).should == "update ranges" @ability.unauthorized_message(:update, Array).should == "update array"
@ability.unauthorized_message(:edit, 1..3).should == "edit ranges" @ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
# @ability.unauthorized_message(:update, ArgumentError).should == "update argument error" @ability.unauthorized_message(:edit, 1..3).should == "edit range"
end end
end end
it "merges the rules from another ability" do describe "#merge" do
it "should add the rules from the given ability" do
@ability.can :use, :tools @ability.can :use, :tools
another_ability = Object.new another_ability = Object.new
another_ability.extend(CanCan::Ability) another_ability.extend(CanCan::Ability)
@@ -538,4 +454,5 @@ describe CanCan::Ability do
@ability.can?(:use, :search).should be_true @ability.can?(:use, :search).should be_true
@ability.send(:rules).size.should == 2 @ability.send(:rules).size.should == 2
end end
end
end end

View File

@@ -2,63 +2,98 @@ require "spec_helper"
describe CanCan::ControllerAdditions do describe CanCan::ControllerAdditions do
before(:each) do before(:each) do
@params = HashWithIndifferentAccess.new
@controller_class = Class.new @controller_class = Class.new
@controller = @controller_class.new @controller = @controller_class.new
@controller.stub(:params) { @params } stub(@controller).params { {} }
@controller.stub(:current_user) { :current_user } stub(@controller).current_user { :current_user }
@controller_class.should_receive(:helper_method).with(:can?, :cannot?, :current_ability) mock(@controller_class).helper_method(:can?, :cannot?, :current_ability)
@controller_class.send(:include, CanCan::ControllerAdditions) @controller_class.send(:include, CanCan::ControllerAdditions)
end end
it "raises ImplementationRemoved when attempting to call load/authorize/skip/check calls on a controller" do it "should raise ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do
lambda { @controller_class.load_resource }.should raise_error(CanCan::ImplementationRemoved) lambda { @controller.unauthorized! }.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 end
it "authorize! should pass args to current ability" do it "authorize! should assign @_authorized instance variable and pass args to current ability" do
@controller.current_ability.should_receive(:authorize!).with(:foo, :bar) mock(@controller.current_ability).authorize!(:foo, :bar)
@controller.authorize!(:foo, :bar) @controller.authorize!(:foo, :bar)
@controller.instance_variable_get(:@_authorized).should be_true
end end
it "provides a can? and cannot? methods which go through the current ability" do 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
@controller.current_ability.should be_kind_of(Ability) @controller.current_ability.should be_kind_of(Ability)
@controller.can?(:foo, :bar).should be_false @controller.can?(:foo, :bar).should be_false
@controller.cannot?(:foo, :bar).should be_true @controller.cannot?(:foo, :bar).should be_true
end end
it "load_and_authorize_resource adds a before filter which passes call to ControllerResource" do it "load_and_authorize_resource should setup a before filter which passes call to ControllerResource" do
controller_resource = double("controller_resource") stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.load_and_authorize_resource
controller_resource.should_receive(:process) mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
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 @controller_class.load_and_authorize_resource :foo => :bar
end end
it "load_and_authorize_resource passes first argument as the resource name" do it "load_and_authorize_resource should properly pass first argument as the resource name" do
controller_resource = double("controller_resource") stub(CanCan::ControllerResource).new(@controller, :project, :foo => :bar).mock!.load_and_authorize_resource
controller_resource.should_receive(:process) mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
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 @controller_class.load_and_authorize_resource :project, :foo => :bar
end end
it "load_and_authorize_resource passes :only, :except, :if, :unless options to before filter" do it "load_and_authorize_resource with :prepend should prepend the before filter" do
controller_resource = double("controller_resource") mock(@controller_class).prepend_before_filter({})
controller_resource.should_receive(:process) @controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
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 end
it "load_and_authorize_resource with :prepend prepends the before filter" do it "authorize_resource should setup a before filter which passes call to ControllerResource" do
@controller_class.should_receive(:prepend_before_filter).with({}) stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
@controller_class.load_and_authorize_resource :foo => :bar, :prepend => true 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 end
it "cancan_resource_class should be ControllerResource by default" do it "cancan_resource_class should be ControllerResource by default" do
@@ -66,53 +101,37 @@ describe CanCan::ControllerAdditions do
end end
it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do
@controller.class.stub(:ancestors) { ["InheritedResources::Actions"] } stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
@controller.class.cancan_resource_class.should == CanCan::InheritedResource @controller.class.cancan_resource_class.should == CanCan::InheritedResource
end end
it "enable_authorization should call authorize! with controller and action name" do it "cancan_skipper should be an empty hash with :authorize and :load options and remember changes" do
@params.merge!(:controller => "projects", :action => "create") @controller_class.cancan_skipper.should == {:authorize => {}, :load => {}}
@controller.should_receive(:authorize!).with("create", "projects") @controller_class.cancan_skipper[:load] = true
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar).and_yield(@controller) @controller_class.cancan_skipper[:load].should == true
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar)
@controller_class.enable_authorization(:only => :foo, :except => :bar)
end end
it "enable_authorization should raise InsufficientAuthorizationCheck when not fully authoried" do it "skip_authorize_resource should add itself to the cancan skipper with given model name and options" do
@params.merge!(:controller => "projects", :action => "create") @controller_class.skip_authorize_resource(:project, :only => [:index, :show])
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar) @controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar).and_yield(@controller) @controller_class.skip_authorize_resource(:only => [:index, :show])
lambda { @controller_class.cancan_skipper[:authorize][nil].should == {:only => [:index, :show]}
@controller_class.enable_authorization(:only => :foo, :except => :bar) @controller_class.skip_authorize_resource(:article)
}.should raise_error(CanCan::InsufficientAuthorizationCheck) @controller_class.cancan_skipper[:authorize][:article].should == {}
end end
it "enable_authorization should not call authorize! when :if is false" do it "skip_load_resource should add itself to the cancan skipper with given model name and options" do
@authorize_called = false @controller_class.skip_load_resource(:project, :only => [:index, :show])
@controller.stub(:authorize?) { false } @controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
@controller.stub(:authorize!) { @authorize_called = true } @controller_class.skip_load_resource(:only => [:index, :show])
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller) @controller_class.cancan_skipper[:load][nil].should == {:only => [:index, :show]}
@controller_class.should_receive(:after_filter).with({}).and_yield(@controller) @controller_class.skip_load_resource(:article)
@controller_class.enable_authorization(:if => :authorize?) @controller_class.cancan_skipper[:load][:article].should == {}
@authorize_called.should be_false
end end
it "enable_authorization should not call authorize! when :unless is true" do it "skip_load_and_authore_resource should add itself to the cancan skipper with given model name and options" do
@authorize_called = false @controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show])
@controller.stub(:engine_controller?) { true } @controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
@controller.stub(:authorize!) { @authorize_called = true } @controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
@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
end end

View File

@@ -2,63 +2,68 @@ require "spec_helper"
describe CanCan::ControllerResource do describe CanCan::ControllerResource do
before(:each) do before(:each) do
Project.delete_all
Category.delete_all
@params = HashWithIndifferentAccess.new(:controller => "projects") @params = HashWithIndifferentAccess.new(:controller => "projects")
@controller_class = Class.new @controller_class = Class.new
@controller = @controller_class.new @controller = @controller_class.new
@ability = Ability.new(nil) @ability = Ability.new(nil)
@controller.stub(:params) { @params } stub(@controller).params { @params }
@controller.stub(:current_ability) { @ability } stub(@controller).current_ability { @ability }
@controller.stub(:authorize!) { |*args| @ability.authorize!(*args) } stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
end end
it "loads the resource into an instance variable if params[:id] is specified" do it "should load the resource into an instance variable if params[:id] is specified" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "does not load resource into an instance variable if already set" do it "should not load resource into an instance variable if already set" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
@controller.instance_variable_set(:@project, :some_project) @controller.instance_variable_set(:@project, :some_project)
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "loads resource for namespaced controller" do it "should properly load resource for namespaced controller" do
project = Project.create! project = Project.create!
@params.merge!(:controller => "admin/projects", :action => "show", :id => project.id) @params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do it "should attempt to load a resource with the same namespace as the controller when using :: for namespace" do
module SomeEngine module MyEngine
class Project < ::Project; end class Project < ::Project; end
end end
project = SomeEngine::Project.create!
@params.merge!(:controller => "SomeEngine::ProjectsController", :action => "show", :id => project.id) project = MyEngine::Project.create!
CanCan::ControllerResource.new(@controller, :load => true).process @params.merge!(:controller => "MyEngine::ProjectsController", :action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
# Rails includes namespace in params, see issue #349 # Rails includes namespace in params, see issue #349
it "creates through the namespaced params" do it "should create through the namespaced params" do
module SomeEngine module MyEngine
class Project < ::Project; end class Project < ::Project; end
end end
@params.merge!(:controller => "SomeEngine::ProjectsController", :action => "create", :some_engine_project => {:name => "foobar"})
CanCan::ControllerResource.new(@controller, :load => true).process @params.merge!(:controller => "MyEngine::ProjectsController", :action => "create", :my_engine_project => {:name => "foobar"})
resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "foobar" @controller.instance_variable_get(:@project).name.should == "foobar"
end end
it "loads resource for namespaced controller when using '::' for namespace" do it "should properly load resource for namespaced controller when using '::' for namespace" do
project = Project.create! project = Project.create!
@params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id) @params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
@@ -68,484 +73,419 @@ describe CanCan::ControllerResource do
end end
@ability.can(:index, "admin/dashboard") @ability.can(:index, "admin/dashboard")
@params.merge!(:controller => "admin/dashboard", :action => "index") @params.merge!(:controller => "admin/dashboard", :action => "index")
@controller.authorize!(:index, "admin/dashboard")
resource = CanCan::ControllerResource.new(@controller, :authorize => true) resource = CanCan::ControllerResource.new(@controller, :authorize => true)
resource.send(:resource_class).should == Admin::Dashboard resource.send(:resource_class).should == Admin::Dashboard
end end
it "builds a new resource with hash if params[:id] is not specified and authorize on each attribute" do it "should build a new resource with hash if params[:id] is not specified" do
@params.merge!(:action => "create", :project => {:name => "foobar"}) @params.merge!(:action => "create", :project => {:name => "foobar"})
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "foobar" @controller.instance_variable_get(:@project).name.should == "foobar"
end end
it "builds a new resource for namespaced model with hash if params[:id] is not specified" do it "should build a new resource for namespaced model with hash if params[:id] is not specified" do
module SomeEngine @params.merge!(:action => "create", 'sub_project' => {:name => "foobar"})
class Project < ::Project; end resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
end resource.load_resource
@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" @controller.instance_variable_get(:@project).name.should == "foobar"
end end
it "builds a new resource with attributes from current ability" do 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
@params.merge!(:action => "new") @params.merge!(:action => "new")
@ability.can(:create, :projects, :name => "from conditions") @ability.can(:create, Project, :name => "from conditions")
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "from conditions" @controller.instance_variable_get(:@project).name.should == "from conditions"
end end
it "overrides initial attributes with params" do it "should override initial attributes with params" do
@params.merge!(:action => "new", :project => {:name => "from params"}) @params.merge!(:action => "new", :project => {:name => "from params"})
@ability.can(:create, :projects, :name => "from conditions") @ability.can(:create, Project, :name => "from conditions")
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "from params" @controller.instance_variable_get(:@project).name.should == "from params"
end end
it "builds a collection when on index action when class responds to accessible_by and mark ability as fully authorized" do it "should build a collection when on index action when class responds to accessible_by" do
Project.stub(:accessible_by).with(@ability, :index) { :found_projects } stub(Project).accessible_by(@ability, :index) { :found_projects }
@params[:action] = "index" @params[:action] = "index"
CanCan::ControllerResource.new(@controller, :project, :load => true).process resource = CanCan::ControllerResource.new(@controller, :project)
resource.load_resource
@controller.instance_variable_get(:@project).should be_nil @controller.instance_variable_get(:@project).should be_nil
@controller.instance_variable_get(:@projects).should == :found_projects @controller.instance_variable_get(:@projects).should == :found_projects
@ability.should be_fully_authorized(:index, :projects)
end end
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 it "should not build a collection when on index action when class does not respond to accessible_by" do
class CustomModel
end
@params[:controller] = "custom_models"
@params[:action] = "index" @params[:action] = "index"
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
@controller.instance_variable_get(:@project).should be_nil resource.load_resource
@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_get(:@project).should be_nil
@controller.instance_variable_defined?(:@projects).should be_false @controller.instance_variable_defined?(:@projects).should be_false
end end
it "does not authorize resource in collection action" do 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
@params[:action] = "index" @params[:action] = "index"
@controller.instance_variable_set(:@project, :some_project) @controller.instance_variable_set(:@project, :some_project)
@controller.stub(:authorize!).with(:index, :projects) { raise CanCan::Unauthorized } stub(@controller).authorize!(:index, Project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :authorize => true) resource = CanCan::ControllerResource.new(@controller)
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "authorizes parent resource in collection action" do it "should authorize parent resource in collection action" do
@params[:action] = "index" @params[:action] = "index"
@controller.instance_variable_set(:@category, :some_category) @controller.instance_variable_set(:@category, :some_category)
@controller.stub(:authorize!).with(:show, :some_category) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, :some_category) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true, :authorize => true) resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "performs authorization using controller action and loaded model" do it "should perform authorization using controller action and loaded model" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
@controller.instance_variable_set(:@project, :some_project) @controller.instance_variable_set(:@project, :some_project)
@controller.stub(:authorize!).with(:show, :some_project) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :authorize => true) resource = CanCan::ControllerResource.new(@controller)
lambda { resource.process }.should raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "does not perform authorization using controller action when no loaded model" do it "should perform authorization using controller action and non loaded model" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
@controller.stub(:authorize!).with(:show, :projects) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, Project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :authorize => true) resource = CanCan::ControllerResource.new(@controller)
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "does not build a single resource when on custom collection action even with id" do it "should call load_resource and authorize_resource for load_and_authorize_resource" do
@params.merge!(:action => "sort", :id => 123) @params.merge!(:action => "show", :id => "123")
CanCan::ControllerResource.new(@controller, :load => true, :collection => [:sort, :list]).process 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
@controller.instance_variable_get(:@project).should be_nil @controller.instance_variable_get(:@project).should be_nil
end end
it "loads a collection resource when on custom action with no id param" do it "should load a collection resource when on custom action with no id param" do
Project.stub(:accessible_by).with(@ability, :sort) { :found_projects } stub(Project).accessible_by(@ability, :sort) { :found_projects }
@params[:action] = "sort" @params[:action] = "sort"
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should be_nil @controller.instance_variable_get(:@project).should be_nil
@controller.instance_variable_get(:@projects).should == :found_projects @controller.instance_variable_get(:@projects).should == :found_projects
end end
it "builds a resource when on custom new action even when params[:id] exists" do it "should build a resource when on custom new action even when params[:id] exists" do
@params.merge!(:action => "build", :id => 123) @params.merge!(:action => "build", :id => "123")
Project.stub(:new) { :some_project } stub(Project).new { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :new => :build).process resource = CanCan::ControllerResource.new(@controller, :new => :build)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "does not try to load resource for other action if params[:id] is undefined" do it "should not try to load resource for other action if params[:id] is undefined" do
@params[:action] = "list" @params[:action] = "list"
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).should be_nil @controller.instance_variable_get(:@project).should be_nil
end end
it "is a parent resource when name is provided which doesn't match controller" do it "should be a parent resource when name is provided which doesn't match controller" do
resource = CanCan::ControllerResource.new(@controller, :category) resource = CanCan::ControllerResource.new(@controller, :category)
resource.should be_parent resource.should be_parent
end end
it "does not be a parent resource when name is provided which matches controller" do it "should not be a parent resource when name is provided which matches controller" do
resource = CanCan::ControllerResource.new(@controller, :project) resource = CanCan::ControllerResource.new(@controller, :project)
resource.should_not be_parent resource.should_not be_parent
end end
it "is parent if specified in options" do it "should be parent if specified in options" do
resource = CanCan::ControllerResource.new(@controller, :project, {:parent => true}) resource = CanCan::ControllerResource.new(@controller, :project, {:parent => true})
resource.should be_parent resource.should be_parent
end end
it "does not be parent if specified in options" do it "should not be parent if specified in options" do
resource = CanCan::ControllerResource.new(@controller, :category, {:parent => false}) resource = CanCan::ControllerResource.new(@controller, :category, {:parent => false})
resource.should_not be_parent resource.should_not be_parent
end end
it "has the specified resource_class if name is passed to load_resource" do it "should have the specified resource_class if 'name' is passed to load_resource" do
resource = CanCan::ControllerResource.new(@controller, :category) class Section
resource.send(:resource_class).should == Category
end end
it "loads parent resource through proper id parameter" do resource = CanCan::ControllerResource.new(@controller, :section)
resource.send(:resource_class).should == Section
end
it "should load parent resource through proper id parameter" do
project = Project.create! project = Project.create!
@params.merge!(:action => "index", :project_id => project.id) @params.merge!(:controller => "categories", :action => "index", :project_id => project.id)
CanCan::ControllerResource.new(@controller, :project, :load => true, :parent => true).process resource = CanCan::ControllerResource.new(@controller, :project)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "loads resource through the association of another parent resource using instance variable" do it "should load resource through the association of another parent resource using instance variable" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
category = double("category", :projects => double("projects")) category = Object.new
category.projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process stub(category).projects.stub!.find("123") { :some_project }
resource = CanCan::ControllerResource.new(@controller, :through => :category)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "loads resource through the custom association name" do it "should load resource through the custom association name" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
category = double("category", :custom_projects => double("custom_projects")) category = Object.new
category.custom_projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :through_association => :custom_projects).process stub(category).custom_projects.stub!.find("123") { :some_project }
resource = CanCan::ControllerResource.new(@controller, :through => :category, :through_association => :custom_projects)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "loads resource through the association of another parent resource using method" do it "should load resource through the association of another parent resource using method" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
category = double("category", :projects => double("projects")) category = Object.new
@controller.stub(:category) { category } stub(@controller).category { category }
category.projects.stub(:find).with(123) { :some_project } stub(category).projects.stub!.find("123") { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process resource = CanCan::ControllerResource.new(@controller, :through => :category)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "does not load through parent resource if instance isn't loaded when shallow" do it "should not load through parent resource if instance isn't loaded when shallow" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :shallow => true).process resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "raises Unauthorized when attempting to load resource through nil" do it "should raise AccessDenied when attempting to load resource through nil" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :load => true, :through => :category) resource = CanCan::ControllerResource.new(@controller, :through => :category)
lambda { lambda {
resource.process resource.load_resource
}.should raise_error(CanCan::Unauthorized) { |exception| }.should raise_error(CanCan::AccessDenied) { |exception|
exception.action.should == :show exception.action.should == :show
exception.subject.should == :projects exception.subject.should == Project
} }
@controller.instance_variable_get(:@project).should be_nil @controller.instance_variable_get(:@project).should be_nil
end end
it "named resources should be loaded independently of the controller name" do it "should authorize nested resource through parent association on index action" 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") @params.merge!(:action => "index")
category = Object.new category = Object.new
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
@controller.stub(:authorize!).with(:index, category => :projects) { raise CanCan::Unauthorized } stub(@controller).authorize!(:index, category => Project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :authorize => true, :through => :category) resource = CanCan::ControllerResource.new(@controller, :through => :category)
lambda { resource.process }.should raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "loads through first matching if multiple are given" do it "should load through first matching if multiple are given" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
category = double("category", :projects => double("projects")) category = Object.new
category.projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => [:category, :user]).process stub(category).projects.stub!.find("123") { :some_project }
resource = CanCan::ControllerResource.new(@controller, :through => [:category, :user])
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "finds record through has_one association with :singleton option without id param" do it "should find record through has_one association with :singleton option without id param" do
@params.merge!(:action => "show", :id => nil) @params.merge!(:action => "show", :id => nil)
category = Object.new category = Object.new
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
category.stub(:project) { :some_project } stub(category).project { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "does not build record through has_one association with :singleton option because it can cause it to delete it in the database" do it "should 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"}) @params.merge!(:action => "create", :project => {:name => "foobar"})
category = Category.new category = Category.new
@controller.instance_variable_set(:@category, category) @controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "foobar" @controller.instance_variable_get(:@project).name.should == "foobar"
@controller.instance_variable_get(:@project).category.should == category @controller.instance_variable_get(:@project).category.should == category
end end
it "finds record through has_one association with :singleton and :shallow options" do it "should find record through has_one association with :singleton and :shallow options" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "builds record through has_one association with :singleton and :shallow options" do it "should build record through has_one association with :singleton and :shallow options" do
@params.merge!(:action => "create", :project => {:name => "foobar"}) @params.merge!(:action => "create", :project => {:name => "foobar"})
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "foobar" @controller.instance_variable_get(:@project).name.should == "foobar"
end end
it "only authorizes :show action on parent resource" do it "should only authorize :show action on parent resource" do
project = Project.create! project = Project.create!
@params.merge!(:action => "new", :project_id => project.id) @params.merge!(:action => "new", :project_id => project.id)
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true, :parent => true) resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized) lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "authorizes update action before setting attributes" do it "should load the model using a custom class" 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! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :class => Project).process resource = CanCan::ControllerResource.new(@controller, :class => Project)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "loads the model using a custom namespaced class" do it "should load the model using a custom namespaced class" do
module SomeEngine project = Sub::Project.create!
class Project < ::Project; end
end
project = SomeEngine::Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :class => SomeEngine::Project).process resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "does not authorize based on resource name if class is false because we don't do class level authorization anymore" do it "should authorize based on resource name if class is false" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => "123")
@controller.stub(:authorize!).with(:show, :projects) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, :project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :authorize => true, :class => false) resource = CanCan::ControllerResource.new(@controller, :class => false)
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized) lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "loads and authorize using custom instance name" do it "should load and authorize using custom instance name" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :id => project.id) @params.merge!(:action => "show", :id => project.id)
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized } stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :load => true, :authorize => true, :instance_name => :custom_project) resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project)
lambda { resource.process }.should raise_error(CanCan::Unauthorized) lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
@controller.instance_variable_get(:@custom_project).should == project @controller.instance_variable_get(:@custom_project).should == project
end end
it "loads resource using custom ID param" do it "should load resource using custom ID param" do
project = Project.create! project = Project.create!
@params.merge!(:action => "show", :the_project => project.id) @params.merge!(:action => "show", :the_project => project.id)
resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project, :load => true) resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
resource.process resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "loads resource using custom find_by attribute" do # 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
project = Project.create!(:name => "foo") project = Project.create!(:name => "foo")
@params.merge!(:action => "show", :id => "foo") @params.merge!(:action => "show", :id => "foo")
CanCan::ControllerResource.new(@controller, :load => true, :find_by => :name).process resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "authorizes each new attribute in the create action" do it "should allow full find method to be passed into find_by option" 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") project = Project.create!(:name => "foo")
@params.merge!(:action => "show", :id => "foo") @params.merge!(:action => "show", :id => "foo")
CanCan::ControllerResource.new(@controller, :find_by => :find_by_name, :load => true).process resource = CanCan::ControllerResource.new(@controller, :find_by => :find_by_name)
resource.load_resource
@controller.instance_variable_get(:@project).should == project @controller.instance_variable_get(:@project).should == project
end end
it "authorizes each new attribute in the update action" do it "should raise ImplementationRemoved when adding :name option" do
@params.merge!(:action => "update", :id => 123, :project => {:name => "foo"}) lambda {
@controller.instance_variable_set(:@project, :some_project) CanCan::ControllerResource.new(@controller, :name => :foo)
@ability.should_receive(:authorize!).with(:update, :some_project, :name) }.should raise_error(CanCan::ImplementationRemoved)
CanCan::ControllerResource.new(@controller, :authorize => true).process
end end
it "fetches member through method when instance variable is not provided" do it "should raise ImplementationRemoved exception when specifying :resource option since it is no longer used" do
@controller.stub(:project) { :some_project } lambda {
@params.merge!(:action => "show", :id => 123) CanCan::ControllerResource.new(@controller, :resource => Project)
@controller.stub(:authorize!).with(:show, :some_project) { raise CanCan::Unauthorized } }.should raise_error(CanCan::ImplementationRemoved)
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end end
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do it "should raise ImplementationRemoved exception when passing :nested option" do
module Namespaced lambda {
class Project < ::Project; end CanCan::ControllerResource.new(@controller, :nested => :project)
end }.should raise_error(CanCan::ImplementationRemoved)
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 end
# Rails includes namespace in params, see issue #349 it "should skip resource behavior for :only actions in array" do
it "creates through namespaced params" do stub(@controller_class).cancan_skipper { {:load => {nil => {:only => [:index, :show]}}} }
module Namespaced @params.merge!(:action => "index")
class Project < ::Project; end CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
end CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
@params.merge!(:controller => "Namespaced::ProjectsController", :action => "create", :namespaced_project => {:name => "foobar"}) @params.merge!(:action => "show")
CanCan::ControllerResource.new(@controller, :load => true).process CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
@controller.instance_variable_get(:@project).name.should == "foobar" @params.merge!(:action => "other_action")
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
end end
it "should properly authorize resource for namespaced controller" do it "should skip resource behavior for :only one action on resource" do
@ability.can(:index, "admin/dashboard") stub(@controller_class).cancan_skipper { {:authorize => {:project => {:only => :index}}} }
@params.merge!(:controller => "admin/dashboard", :action => "index") @params.merge!(:action => "index")
@controller.authorize!(:index, "admin/dashboard") CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
resource = CanCan::ControllerResource.new(@controller, :authorize => true).process CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized) @params.merge!(:action => "other_action")
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
end end
# it "raises ImplementationRemoved when adding :name option" do it "should skip resource behavior :except actions in array" do
# lambda { stub(@controller_class).cancan_skipper { {:load => {nil => {:except => [:index, :show]}}} }
# CanCan::ControllerResource.new(@controller, :name => :foo) @params.merge!(:action => "index")
# }.should raise_error(CanCan::ImplementationRemoved) CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
# end @params.merge!(:action => "show")
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
# it "raises ImplementationRemoved exception when specifying :resource option since it is no longer used" do @params.merge!(:action => "other_action")
# lambda { CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
# CanCan::ControllerResource.new(@controller, :resource => Project) CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
# }.should raise_error(CanCan::ImplementationRemoved) end
# end
#
# it "raises ImplementationRemoved exception when passing :nested option" do
# lambda {
# CanCan::ControllerResource.new(@controller, :nested => :project)
# }.should raise_error(CanCan::ImplementationRemoved)
# end
# it "skips resource behavior for :only actions in array" do it "should skip resource behavior :except one action on resource" do
# @controller_class.stub(:cancan_skipper) { {:load => {nil => {:only => [:index, :show]}}} } stub(@controller_class).cancan_skipper { {:authorize => {:project => {:except => :index}}} }
# @params.merge!(:action => "index") @params.merge!(:action => "index")
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_true CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
# CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false @params.merge!(:action => "other_action")
# @params.merge!(:action => "show") CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_true CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
# @params.merge!(:action => "other_action") end
# CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
# end it "should skip loading and authorization" do
# stub(@controller_class).cancan_skipper { {:authorize => {nil => {}}, :load => {nil => {}}} }
# it "skips resource behavior for :only one action on resource" do @params.merge!(:action => "new")
# @controller_class.stub(:cancan_skipper) { {:authorize => {:project => {:only => :index}}} } resource = CanCan::ControllerResource.new(@controller)
# @params.merge!(:action => "index") lambda { resource.load_and_authorize_resource }.should_not raise_error
# CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false @controller.instance_variable_get(:@project).should be_nil
# CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true end
# @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 end

View File

@@ -1,17 +1,17 @@
require "spec_helper" require "spec_helper"
describe CanCan::Unauthorized do describe CanCan::AccessDenied do
describe "with action and subject" do describe "with action and subject" do
before(:each) do before(:each) do
@exception = CanCan::Unauthorized.new(nil, :some_action, :some_subject) @exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject)
end end
it "has action and subject accessors" do it "should have action and subject accessors" do
@exception.action.should == :some_action @exception.action.should == :some_action
@exception.subject.should == :some_subject @exception.subject.should == :some_subject
end end
it "has a changable default message" do it "should have a changable default message" do
@exception.message.should == "You are not authorized to access this page." @exception.message.should == "You are not authorized to access this page."
@exception.default_message = "Unauthorized!" @exception.default_message = "Unauthorized!"
@exception.message.should == "Unauthorized!" @exception.message.should == "Unauthorized!"
@@ -20,15 +20,15 @@ describe CanCan::Unauthorized do
describe "with only a message" do describe "with only a message" do
before(:each) do before(:each) do
@exception = CanCan::Unauthorized.new("Access denied!") @exception = CanCan::AccessDenied.new("Access denied!")
end end
it "has nil action and subject" do it "should have nil action and subject" do
@exception.action.should be_nil @exception.action.should be_nil
@exception.subject.should be_nil @exception.subject.should be_nil
end end
it "has passed message" do it "should have passed message" do
@exception.message.should == "Access denied!" @exception.message.should == "Access denied!"
end end
end end
@@ -40,17 +40,17 @@ describe CanCan::Unauthorized do
it "uses i18n for the default message" do it "uses i18n for the default message" do
I18n.backend.store_translations :en, :unauthorized => {:default => "This is a different message"} I18n.backend.store_translations :en, :unauthorized => {:default => "This is a different message"}
@exception = CanCan::Unauthorized.new @exception = CanCan::AccessDenied.new
@exception.message.should == "This is a different message" @exception.message.should == "This is a different message"
end end
it "defaults to a nice message" do it "defaults to a nice message" do
@exception = CanCan::Unauthorized.new @exception = CanCan::AccessDenied.new
@exception.message.should == "You are not authorized to access this page." @exception.message.should == "You are not authorized to access this page."
end end
it "does not use translation if a message is given" do it "does not use translation if a message is given" do
@exception = CanCan::Unauthorized.new("Hey! You're not welcome here") @exception = CanCan::AccessDenied.new("Hey! You're not welcome here")
@exception.message.should == "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." @exception.message.should_not == "You are not authorized to access this page."
end end

View File

@@ -6,53 +6,55 @@ describe CanCan::InheritedResource do
@controller_class = Class.new @controller_class = Class.new
@controller = @controller_class.new @controller = @controller_class.new
@ability = Ability.new(nil) @ability = Ability.new(nil)
@controller.stub(:params) { @params } stub(@controller).params { @params }
@controller.stub(:current_ability) { @ability } stub(@controller).current_ability { @ability }
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} } stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
end end
it "show should load resource through @controller.resource" do it "show should load resource through @controller.resource" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => 123)
@controller.stub(:resource) { :project_resource } stub(@controller).resource { :project_resource }
CanCan::InheritedResource.new(@controller, :load => true).process CanCan::InheritedResource.new(@controller).load_resource
@controller.instance_variable_get(:@project).should == :project_resource @controller.instance_variable_get(:@project).should == :project_resource
end end
it "new should load through @controller.build_resource" do it "new should load through @controller.build_resource" do
@params[:action] = "new" @params[:action] = "new"
@controller.stub(:build_resource) { :project_resource } stub(@controller).build_resource { :project_resource }
CanCan::InheritedResource.new(@controller, :load => true).process CanCan::InheritedResource.new(@controller).load_resource
@controller.instance_variable_get(:@project).should == :project_resource @controller.instance_variable_get(:@project).should == :project_resource
end end
it "index should load through @controller.association_chain when parent" do it "index should load through @controller.association_chain when parent" do
@params[:action] = "index" @params[:action] = "index"
@controller.stub(:association_chain) { @controller.instance_variable_set(:@project, :project_resource) } stub(@controller).association_chain { @controller.instance_variable_set(:@project, :project_resource) }
CanCan::InheritedResource.new(@controller, :load => true, :parent => true).process CanCan::InheritedResource.new(@controller, :parent => true).load_resource
@controller.instance_variable_get(:@project).should == :project_resource @controller.instance_variable_get(:@project).should == :project_resource
end end
it "index should load through @controller.end_of_association_chain" do it "index should load through @controller.end_of_association_chain" do
@params[:action] = "index" @params[:action] = "index"
Project.stub(:accessible_by).with(@ability, :index) { :projects } stub(Project).accessible_by(@ability, :index) { :projects }
@controller.stub(:end_of_association_chain) { Project } stub(@controller).end_of_association_chain { Project }
CanCan::InheritedResource.new(@controller, :load => true).process CanCan::InheritedResource.new(@controller).load_resource
@controller.instance_variable_get(:@projects).should == :projects @controller.instance_variable_get(:@projects).should == :projects
end end
it "should build a new resource with attributes from current ability" do it "should build a new resource with attributes from current ability" do
@params[:action] = "new" @params[:action] = "new"
@ability.can(:create, :projects, :name => "from conditions") @ability.can(:create, Project, :name => "from conditions")
@controller.stub(:build_resource) { Struct.new(:name).new } stub(@controller).build_resource { Struct.new(:name).new }
CanCan::InheritedResource.new(@controller, :load => true).process resource = CanCan::InheritedResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "from conditions" @controller.instance_variable_get(:@project).name.should == "from conditions"
end end
it "should override initial attributes with params" do it "should override initial attributes with params" do
@params.merge!(:action => "new", :project => {:name => "from params"}) @params.merge!(:action => "new", :project => {:name => "from params"})
@ability.can(:create, :projects, :name => "from conditions") @ability.can(:create, Project, :name => "from conditions")
@controller.stub(:build_resource) { Struct.new(:name).new } stub(@controller).build_resource { Struct.new(:name).new }
CanCan::ControllerResource.new(@controller, :load => true).process resource = CanCan::ControllerResource.new(@controller)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "from params" @controller.instance_variable_get(:@project).name.should == "from params"
end end
end end

View File

@@ -3,13 +3,13 @@ require "spec_helper"
describe "be_able_to" do describe "be_able_to" do
it "delegates to can?" do it "delegates to can?" do
object = Object.new object = Object.new
object.should_receive(:can?).with(:read, 123) { true } mock(object).can?(:read, 123) { true }
object.should be_able_to(:read, 123) object.should be_able_to(:read, 123)
end end
it "reports a nice failure message for should" do it "reports a nice failure message for should" do
object = Object.new object = Object.new
object.should_receive(:can?).with(:read, 123) { false } mock(object).can?(:read, 123) { false }
expect do expect do
object.should be_able_to(:read, 123) object.should be_able_to(:read, 123)
end.should raise_error('expected to 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 it "reports a nice failure message for should not" do
object = Object.new object = Object.new
object.should_receive(:can?).with(:read, 123) { true } mock(object).can?(:read, 123) { true }
expect do expect do
object.should_not be_able_to(:read, 123) object.should_not be_able_to(:read, 123)
end.should raise_error('expected not to 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 it "delegates additional arguments to can? and reports in failure message" do
object = Object.new object = Object.new
object.should_receive(:can?).with(:read, 123, 456) { false } mock(object).can?(:read, 123, 456) { false }
expect do expect do
object.should be_able_to(:read, 123, 456) object.should be_able_to(:read, 123, 456)
end.should raise_error('expected to be able to :read 123 456') end.should raise_error('expected to be able to :read 123 456')

View File

@@ -1,31 +1,53 @@
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record" if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
require "spec_helper" require "spec_helper"
class Category ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
has_many :articles
end
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
belongs_to :category
has_many :comments
end
class Comment < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.integer :article_id
t.boolean :spam
end
belongs_to :article
end
describe CanCan::ModelAdapters::ActiveRecordAdapter do describe CanCan::ModelAdapters::ActiveRecordAdapter do
with_model :category do
table do |t|
t.boolean "visible"
end
model do
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"
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"
end
model do
belongs_to :article
end
end
with_model :user do
table do |t|
end
model do
has_many :articles
end
end
before(:each) do before(:each) do
Article.delete_all Article.delete_all
Comment.delete_all Comment.delete_all
@@ -35,38 +57,38 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
@comment_table = Comment.table_name @comment_table = Comment.table_name
end end
it "is for only active record classes" do it "should be for only active record classes" do
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article) CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
end end
it "finds record" do it "should find record" do
article = Article.create! article = Article.create!
CanCan::ModelAdapters::ActiveRecordAdapter.find(Article, article.id).should == article CanCan::ModelAdapters::ActiveRecordAdapter.find(Article, article.id).should == article
end end
it "does not fetch any records when no abilities are defined" do it "should not fetch any records when no abilities are defined" do
Article.create! Article.create!
Article.accessible_by(@ability).should be_empty Article.accessible_by(@ability).should be_empty
end end
it "fetches all articles when one can read all" do it "should fetch all articles when one can read all" do
@ability.can :read, :articles @ability.can :read, Article
article = Article.create! article = Article.create!
Article.accessible_by(@ability).should == [article] Article.accessible_by(@ability).should == [article]
end end
it "fetches only the articles that are published" do it "should fetch only the articles that are published" do
@ability.can :read, :articles, :published => true @ability.can :read, Article, :published => true
article1 = Article.create!(:published => true) article1 = Article.create!(:published => true)
article2 = Article.create!(:published => false) article2 = Article.create!(:published => false)
Article.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "fetches any articles which are published or secret" do it "should fetch any articles which are published or secret" do
@ability.can :read, :articles, :published => true @ability.can :read, Article, :published => true
@ability.can :read, :articles, :secret => true @ability.can :read, Article, :secret => true
article1 = Article.create!(:published => true, :secret => false) article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true) article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true) article3 = Article.create!(:published => false, :secret => true)
@@ -74,9 +96,9 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
Article.accessible_by(@ability).should == [article1, article2, article3] Article.accessible_by(@ability).should == [article1, article2, article3]
end end
it "fetches only the articles that are published and not secret" do it "should fetch only the articles that are published and not secret" do
@ability.can :read, :articles, :published => true @ability.can :read, Article, :published => true
@ability.cannot :read, :articles, :secret => true @ability.cannot :read, Article, :secret => true
article1 = Article.create!(:published => true, :secret => false) article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true) article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true) article3 = Article.create!(:published => false, :secret => true)
@@ -84,24 +106,23 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
Article.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "only reads comments for articles which are published" do it "should only read comments for articles which are published" do
@ability.can :read, :comments, :article => { :published => true } @ability.can :read, Comment, :article => { :published => true }
comment1 = Comment.create!(:article => Article.create!(:published => true)) comment1 = Comment.create!(:article => Article.create!(:published => true))
comment2 = Comment.create!(:article => Article.create!(:published => false)) comment2 = Comment.create!(:article => Article.create!(:published => false))
Comment.accessible_by(@ability).should == [comment1] Comment.accessible_by(@ability).should == [comment1]
end end
it "only reads comments for visible categories through articles" do it "should only read comments for visible categories through articles" do
pending "does ActiveRecord no longer support a deep nested hash of conditions?" @ability.can :read, Comment, :article => { :category => { :visible => true } }
@ability.can :read, :comments, :article => { :category => { :visible => true } }
comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true))) comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true)))
comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false))) comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false)))
Comment.accessible_by(@ability).should == [comment1] Comment.accessible_by(@ability).should == [comment1]
end end
it "allows conditions in SQL and merge with hash conditions" do it "should allow conditions in SQL and merge with hash conditions" do
@ability.can :read, :articles, :published => true @ability.can :read, Article, :published => true
@ability.can :read, :articles, ["secret=?", true] @ability.can :read, Article, ["secret=?", true]
article1 = Article.create!(:published => true, :secret => false) article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true) article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true) article3 = Article.create!(:published => false, :secret => true)
@@ -109,118 +130,141 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
Article.accessible_by(@ability).should == [article1, article2, article3] Article.accessible_by(@ability).should == [article1, article2, article3]
end end
it "allows a scope for conditions" do it "should allow a scope for conditions" do
@ability.can :read, :articles, Article.where(:secret => true) @ability.can :read, Article, Article.where(:secret => true)
article1 = Article.create!(:secret => true) article1 = Article.create!(:secret => true)
article2 = Article.create!(:secret => false) article2 = Article.create!(:secret => false)
Article.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "fetches only associated records when using with a scope for conditions" do it "should fetch only associated records when using with a scope for conditions" do
@ability.can :read, :articles, Article.where(:secret => true) @ability.can :read, Article, Article.where(:secret => true)
category1 = Category.create!(:visible => false) category1 = Category.create!(:visible => false)
category2 = Category.create!(:visible => true) category2 = Category.create!(:visible => true)
article1 = Article.create!(:secret => true, :category => category1) article1 = Article.create!(:secret => true, :category => category1)
article2 = Article.create!(:secret => true, :category => category2) article2 = Article.create!(:secret => true, :category => category2)
# for some reason the objects aren't comparing equally here so it's necessary to compare by id category1.articles.accessible_by(@ability).should == [article1]
category1.articles.accessible_by(@ability).map(&:id).should == [article1.id]
end end
it "raises an exception when trying to merge scope with other conditions" do it "should raise an exception when trying to merge scope with other conditions" do
@ability.can :read, :articles, :published => true @ability.can :read, Article, :published => true
@ability.can :read, :articles, Article.where(:secret => 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 articles ability.") 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.")
end end
it "does not allow to fetch records when ability with just block present" do it "should not allow to fetch records when ability with just block present" do
@ability.can :read, :articles do @ability.can :read, Article do
false false
end end
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error) lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
end end
it "does not allow to check ability on object against SQL conditions without block" do it "should not allow to check ability on object against SQL conditions without block" do
@ability.can :read, :articles, ["secret=?", true] @ability.can :read, Article, ["secret=?", true]
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error) lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
end end
it "has false conditions if no abilities match" do it "should have false conditions if no abilities match" do
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'" @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end end
it "returns false conditions for cannot clause" do it "should return false conditions for cannot clause" do
@ability.cannot :read, :articles @ability.cannot :read, Article
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'" @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end end
it "returns SQL for single `can` definition in front of default `cannot` condition" do it "should return SQL for single `can` definition in front of default `cannot` condition" do
@ability.cannot :read, :articles @ability.cannot :read, Article
@ability.can :read, :articles, :published => false, :secret => true @ability.can :read, Article, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't']) @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
end end
it "returns true condition for single `can` definition in front of default `can` condition" do it "should return true condition for single `can` definition in front of default `can` condition" do
@ability.can :read, :articles @ability.can :read, Article
@ability.can :read, :articles, :published => false, :secret => true @ability.can :read, Article, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should eq(:secret => true, :published => false) @ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
end end
it "returns `false condition` for single `cannot` definition in front of default `cannot` condition" do it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
@ability.cannot :read, :articles @ability.cannot :read, Article
@ability.cannot :read, :articles, :published => false, :secret => true @ability.cannot :read, Article, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'" @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end end
it "returns `not (sql)` for single `cannot` definition in front of default `can` condition" do it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
@ability.can :read, :articles @ability.can :read, Article
@ability.cannot :read, :articles, :published => false, :secret => true @ability.cannot :read, Article, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')]) @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
end end
it "returns appropriate sql conditions in complex case" do it "should return appropriate sql conditions in complex case" do
@ability.can :read, :articles @ability.can :read, Article
@ability.can :access, :articles, :id => 1 @ability.can :manage, Article, :id => 1
@ability.can :update, :articles, :published => true @ability.can :update, Article, :published => true
@ability.cannot :update, :articles, :secret => true @ability.cannot :update, Article, :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, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
@ability.model_adapter(Article, :access).conditions.should == {:id => 1} @ability.model_adapter(Article, :manage).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) @ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
end end
it "does not forget conditions when calling with SQL string" do it "should return appropriate sql conditions in complex case with nested joins" do
@ability.can :read, :articles, :published => true @ability.can :read, Comment, :article => { :category => { :visible => true } }
@ability.can :read, :articles, ['secret=?', false] @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]
adapter = @ability.model_adapter(Article, :read) adapter = @ability.model_adapter(Article, :read)
2.times do 2.times do
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')] adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
end end
end end
it "has nil joins if no rules" do it "should have nil joins if no rules" do
@ability.model_adapter(Article, :read).joins.should be_nil @ability.model_adapter(Article, :read).joins.should be_nil
end end
it "has nil joins if no nested hashes specified in conditions" do it "should have nil joins if no nested hashes specified in conditions" do
@ability.can :read, :articles, :published => false @ability.can :read, Article, :published => false
@ability.can :read, :articles, :secret => true @ability.can :read, Article, :secret => true
@ability.model_adapter(Article, :read).joins.should be_nil @ability.model_adapter(Article, :read).joins.should be_nil
end end
it "merges separate joins into a single array" do it "should merge separate joins into a single array" do
@ability.can :read, :articles, :project => { :blocked => false } @ability.can :read, Article, :project => { :blocked => false }
@ability.can :read, :articles, :company => { :admin => true } @ability.can :read, Article, :company => { :admin => true }
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect) @ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
end end
it "merges same joins into a single array" do it "should merge same joins into a single array" do
@ability.can :read, :articles, :project => { :blocked => false } @ability.can :read, Article, :project => { :blocked => false }
@ability.can :read, :articles, :project => { :admin => true } @ability.can :read, Article, :project => { :admin => true }
@ability.model_adapter(Article, :read).joins.should == [:project] @ability.model_adapter(Article, :read).joins.should == [:project]
end end
it "restricts articles given a MetaWhere condition" do it "should merge nested and non-nested joins" do
pending @ability.can :read, Article, :project => { :blocked => false }
@ability.can :read, :articles, :priority.lt => 2 @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
article1 = Article.create!(:priority => 1) article1 = Article.create!(:priority => 1)
article2 = Article.create!(:priority => 3) article2 = Article.create!(:priority => 3)
Article.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
@@ -229,7 +273,6 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
end end
it "should merge MetaWhere and non-MetaWhere conditions" do it "should merge MetaWhere and non-MetaWhere conditions" do
pending
@ability.can :read, Article, :priority.lt => 2 @ability.can :read, Article, :priority.lt => 2
@ability.can :read, Article, :priority => 1 @ability.can :read, Article, :priority => 1
article1 = Article.create!(:priority => 1) article1 = Article.create!(:priority => 1)
@@ -239,8 +282,7 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
@ability.should_not be_able_to(:read, article2) @ability.should_not be_able_to(:read, article2)
end end
it "matches any MetaWhere condition" do it "should match any MetaWhere condition" do
pending
adapter = CanCan::ModelAdapters::ActiveRecordAdapter adapter = CanCan::ModelAdapters::ActiveRecordAdapter
article1 = Article.new(:priority => 1, :name => "Hello World") article1 = Article.new(:priority => 1, :name => "Hello World")
adapter.matches_condition?(article1, :priority.eq, 1).should be_true adapter.matches_condition?(article1, :priority.eq, 1).should be_true

View File

@@ -3,20 +3,20 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
DataMapper.setup(:default, 'sqlite::memory:') DataMapper.setup(:default, 'sqlite::memory:')
class DataMapperArticle class Article
include DataMapper::Resource include DataMapper::Resource
property :id, Serial property :id, Serial
property :published, Boolean, :default => false property :published, Boolean, :default => false
property :secret, Boolean, :default => false property :secret, Boolean, :default => false
property :priority, Integer property :priority, Integer
has n, :data_mapper_comments has n, :comments
end end
class DataMapperComment class Comment
include DataMapper::Resource include DataMapper::Resource
property :id, Serial property :id, Serial
property :spam, Boolean, :default => false property :spam, Boolean, :default => false
belongs_to :data_mapper_article belongs_to :article
end end
DataMapper.finalize DataMapper.finalize
@@ -24,92 +24,91 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
describe CanCan::ModelAdapters::DataMapperAdapter do describe CanCan::ModelAdapters::DataMapperAdapter do
before(:each) do before(:each) do
DataMapperArticle.destroy Article.destroy
DataMapperComment.destroy Comment.destroy
@ability = Object.new @ability = Object.new
@ability.extend(CanCan::Ability) @ability.extend(CanCan::Ability)
end end
it "is for only data mapper classes" do it "should be for only data mapper classes" do
CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(DataMapperArticle) CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(Article)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(DataMapperArticle).should == CanCan::ModelAdapters::DataMapperAdapter CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::DataMapperAdapter
end end
it "finds record" do it "should find record" do
article = DataMapperArticle.create article = Article.create
CanCan::ModelAdapters::DataMapperAdapter.find(DataMapperArticle, article.id).should == article CanCan::ModelAdapters::DataMapperAdapter.find(Article, article.id).should == article
end end
it "does not fetch any records when no abilities are defined" do it "should not fetch any records when no abilities are defined" do
DataMapperArticle.create Article.create
DataMapperArticle.accessible_by(@ability).should be_empty Article.accessible_by(@ability).should be_empty
end end
it "fetches all articles when one can read all" do it "should fetch all articles when one can read all" do
@ability.can :read, :data_mapper_articles @ability.can :read, Article
article = DataMapperArticle.create article = Article.create
DataMapperArticle.accessible_by(@ability).should == [article] Article.accessible_by(@ability).should == [article]
end end
it "fetches only the articles that are published" do it "should fetch only the articles that are published" do
@ability.can :read, :data_mapper_articles, :published => true @ability.can :read, Article, :published => true
article1 = DataMapperArticle.create(:published => true) article1 = Article.create(:published => true)
article2 = DataMapperArticle.create(:published => false) article2 = Article.create(:published => false)
DataMapperArticle.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "fetches any articles which are published or secret" do it "should fetch any articles which are published or secret" do
@ability.can :read, :data_mapper_articles, :published => true @ability.can :read, Article, :published => true
@ability.can :read, :data_mapper_articles, :secret => true @ability.can :read, Article, :secret => true
article1 = DataMapperArticle.create(:published => true, :secret => false) article1 = Article.create(:published => true, :secret => false)
article2 = DataMapperArticle.create(:published => true, :secret => true) article2 = Article.create(:published => true, :secret => true)
article3 = DataMapperArticle.create(:published => false, :secret => true) article3 = Article.create(:published => false, :secret => true)
article4 = DataMapperArticle.create(:published => false, :secret => false) article4 = Article.create(:published => false, :secret => false)
DataMapperArticle.accessible_by(@ability).should == [article1, article2, article3] Article.accessible_by(@ability).should == [article1, article2, article3]
end end
it "fetches only the articles that are published and not secret" do it "should fetch 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, Article, :published => true
@ability.can :read, :data_mapper_articles, :published => true @ability.cannot :read, Article, :secret => true
@ability.cannot :read, :data_mapper_articles, :secret => true article1 = Article.create(:published => true, :secret => false)
article1 = DataMapperArticle.create(:published => true, :secret => false) article2 = Article.create(:published => true, :secret => true)
article2 = DataMapperArticle.create(:published => true, :secret => true) article3 = Article.create(:published => false, :secret => true)
article3 = DataMapperArticle.create(:published => false, :secret => true) article4 = Article.create(:published => false, :secret => false)
article4 = DataMapperArticle.create(:published => false, :secret => false) Article.accessible_by(@ability).should == [article1]
DataMapperArticle.accessible_by(@ability).should == [article1]
end end
it "only reads comments for articles which are published" do it "should only read comments for articles which are published" do
@ability.can :read, :data_mapper_comments, :data_mapper_article => { :published => true } @ability.can :read, Comment, :article => { :published => true }
comment1 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => true)) comment1 = Comment.create(:article => Article.create!(:published => true))
comment2 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => false)) comment2 = Comment.create(:article => Article.create!(:published => false))
DataMapperComment.accessible_by(@ability).should == [comment1] Comment.accessible_by(@ability).should == [comment1]
end end
it "allows conditions in SQL and merge with hash conditions" do it "should allow conditions in SQL and merge with hash conditions" do
@ability.can :read, :data_mapper_articles, :published => true @ability.can :read, Article, :published => true
@ability.can :read, :data_mapper_articles, ["secret=?", true] @ability.can :read, Article, ["secret=?", true]
article1 = DataMapperArticle.create(:published => true, :secret => false) article1 = Article.create(:published => true, :secret => false)
article4 = DataMapperArticle.create(:published => false, :secret => false) article4 = Article.create(:published => false, :secret => false)
DataMapperArticle.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "matches gt comparison" do it "should match gt comparison" do
@ability.can :read, :data_mapper_articles, :priority.gt => 3 @ability.can :read, Article, :priority.gt => 3
article1 = DataMapperArticle.create(:priority => 4) article1 = Article.create(:priority => 4)
article2 = DataMapperArticle.create(:priority => 3) article2 = Article.create(:priority => 3)
DataMapperArticle.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
@ability.should be_able_to(:read, article1) @ability.should be_able_to(:read, article1)
@ability.should_not be_able_to(:read, article2) @ability.should_not be_able_to(:read, article2)
end end
it "matches gte comparison" do it "should match gte comparison" do
@ability.can :read, :data_mapper_articles, :priority.gte => 3 @ability.can :read, Article, :priority.gte => 3
article1 = DataMapperArticle.create(:priority => 4) article1 = Article.create(:priority => 4)
article2 = DataMapperArticle.create(:priority => 3) article2 = Article.create(:priority => 3)
article3 = DataMapperArticle.create(:priority => 2) article3 = Article.create(:priority => 2)
DataMapperArticle.accessible_by(@ability).should == [article1, article2] Article.accessible_by(@ability).should == [article1, article2]
@ability.should be_able_to(:read, article1) @ability.should be_able_to(:read, article1)
@ability.should be_able_to(:read, article2) @ability.should be_able_to(:read, article2)
@ability.should_not be_able_to(:read, article3) @ability.should_not be_able_to(:read, article3)

View File

@@ -1,7 +1,7 @@
require "spec_helper" require "spec_helper"
describe CanCan::ModelAdapters::DefaultAdapter do describe CanCan::ModelAdapters::DefaultAdapter do
it "is default for generic classes" do it "should be default for generic classes" do
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Object).should == CanCan::ModelAdapters::DefaultAdapter CanCan::ModelAdapters::AbstractAdapter.adapter_class(Object).should == CanCan::ModelAdapters::DefaultAdapter
end end
end end

View File

@@ -3,11 +3,13 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
class MongoidCategory class MongoidCategory
include Mongoid::Document include Mongoid::Document
references_many :mongoid_projects references_many :mongoid_projects
end end
class MongoidProject class MongoidProject
include Mongoid::Document include Mongoid::Document
referenced_in :mongoid_category referenced_in :mongoid_category
end end
@@ -28,33 +30,33 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
end.each(&:drop) end.each(&:drop)
end end
it "is for only Mongoid classes" do it "should be for only Mongoid classes" do
CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject) CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
end end
it "finds record" do it "should find record" do
project = MongoidProject.create project = MongoidProject.create
CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id).should == project CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id).should == project
end end
it "compares properties on mongoid documents with the conditions hash" do it "should compare properties on mongoid documents with the conditions hash" do
model = MongoidProject.new model = MongoidProject.new
@ability.can :read, :mongoid_projects, :id => model.id @ability.can :read, MongoidProject, :id => model.id
@ability.should be_able_to(:read, model) @ability.should be_able_to(:read, model)
end end
it "is able to read hashes when field is array" do it "should be able to read hashes when field is array" do
one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three']) one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three'])
two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five']) two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five'])
@ability.can :foo, :mongoid_projects, :numbers => 'one' @ability.can :foo, MongoidProject, :numbers => 'one'
@ability.should be_able_to(:foo, one_to_three) @ability.should be_able_to(:foo, one_to_three)
@ability.should_not be_able_to(:foo, two_to_five) @ability.should_not be_able_to(:foo, two_to_five)
end end
it "returns [] when no ability is defined so no records are found" do it "should return [] when no ability is defined so no records are found" do
MongoidProject.create(:title => 'Sir') MongoidProject.create(:title => 'Sir')
MongoidProject.create(:title => 'Lord') MongoidProject.create(:title => 'Lord')
MongoidProject.create(:title => 'Dude') MongoidProject.create(:title => 'Dude')
@@ -62,8 +64,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [] MongoidProject.accessible_by(@ability, :read).entries.should == []
end end
it "returns the correct records based on the defined ability" do it "should return the correct records based on the defined ability" do
@ability.can :read, :mongoid_projects, :title => "Sir" @ability.can :read, MongoidProject, :title => "Sir"
sir = MongoidProject.create(:title => 'Sir') sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord') lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude') dude = MongoidProject.create(:title => 'Dude')
@@ -71,10 +73,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [sir] MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end end
it "returns the correct records when a mix of can and cannot rules in defined ability" do it "should return 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, MongoidProject, :title => 'Sir'
@ability.can :manage, :mongoid_projects, :title => 'Sir' @ability.cannot :destroy, MongoidProject
@ability.cannot :destroy, :mongoid_projects
sir = MongoidProject.create(:title => 'Sir') sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord') lord = MongoidProject.create(:title => 'Lord')
@@ -83,17 +84,17 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :destroy).entries.should == [sir] MongoidProject.accessible_by(@ability, :destroy).entries.should == [sir]
end end
it "takes presedence over rule defined without a condition" do it "should be able to mix empty conditions and hashes" do
@ability.can :read, :mongoid_projects @ability.can :read, MongoidProject
@ability.can :read, :mongoid_projects, :title => 'Sir' @ability.can :read, MongoidProject, :title => 'Sir'
sir = MongoidProject.create(:title => 'Sir') sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord') lord = MongoidProject.create(:title => 'Lord')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir] MongoidProject.accessible_by(@ability, :read).count.should == 2
end end
it "returns everything when the defined ability is access all" do it "should return everything when the defined ability is manage all" do
@ability.can :access, :all @ability.can :manage, :all
sir = MongoidProject.create(:title => 'Sir') sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord') lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude') dude = MongoidProject.create(:title => 'Dude')
@@ -101,8 +102,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude] MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
end end
it "allows a scope for conditions" do it "should allow a scope for conditions" do
@ability.can :read, :mongoid_projects, MongoidProject.where(:title => 'Sir') @ability.can :read, MongoidProject, MongoidProject.where(:title => 'Sir')
sir = MongoidProject.create(:title => 'Sir') sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord') lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude') dude = MongoidProject.create(:title => 'Dude')
@@ -111,9 +112,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
end end
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
it "handles :field.in" do it "should handle :field.in" do
obj = MongoidProject.create(:title => 'Sir') obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"] @ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -126,20 +127,20 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
obj = MongoidProject.create(:title => 'Bird') obj = MongoidProject.create(:title => 'Bird')
@conditions = {:title.nin => ["Fork", "Spoon"]} @conditions = {:title.nin => ["Fork", "Spoon"]}
@ability.can :read, :mongoid_projects, @conditions @ability.can :read, MongoidProject, @conditions
@ability.should be_able_to(:read, obj) @ability.should be_able_to(:read, obj)
end end
it "Calls the base version if there are no mongoid criteria" do it "Calls the base version if there are no mongoid criteria" do
obj = MongoidProject.new(:title => 'Bird') obj = MongoidProject.new(:title => 'Bird')
@conditions = {:id => obj.id} @conditions = {:id => obj.id}
@ability.can :read, :mongoid_projects, @conditions @ability.can :read, MongoidProject, @conditions
@ability.should be_able_to(:read, obj) @ability.should be_able_to(:read, obj)
end end
end end
it "handles :field.nin" do it "should handle :field.nin" do
obj = MongoidProject.create(:title => 'Sir') obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.nin => ["Lord", "Madam"] @ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -147,9 +148,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "handles :field.size" do it "should handle :field.size" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave']) obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, :mongoid_projects, :titles.size => 2 @ability.can :read, MongoidProject, :titles.size => 2
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -157,9 +158,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "handles :field.exists" do it "should handle :field.exists" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave']) obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, :mongoid_projects, :titles.exists => true @ability.can :read, MongoidProject, :titles.exists => true
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -167,9 +168,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "handles :field.gt" do it "should handle :field.gt" do
obj = MongoidProject.create(:age => 50) obj = MongoidProject.create(:age => 50)
@ability.can :read, :mongoid_projects, :age.gt => 45 @ability.can :read, MongoidProject, :age.gt => 45
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -177,9 +178,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "handles instance not saved to database" do it "should handle instance not saved to database" do
obj = MongoidProject.new(:title => 'Sir') obj = MongoidProject.new(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"] @ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
# accessible_by only returns saved records # accessible_by only returns saved records
@@ -190,31 +191,31 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
end end
end end
it "calls where with matching ability conditions" do it "should call where with matching ability conditions" do
obj = MongoidProject.create(:foo => {:bar => 1}) obj = MongoidProject.create(:foo => {:bar => 1})
@ability.can :read, :mongoid_projects, :foo => {:bar => 1} @ability.can :read, MongoidProject, :foo => {:bar => 1}
MongoidProject.accessible_by(@ability, :read).entries.first.should == obj MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
end end
it "excludes from the result if set to cannot" do it "should exclude from the result if set to cannot" do
obj = MongoidProject.create(:bar => 1) obj = MongoidProject.create(:bar => 1)
obj2 = MongoidProject.create(:bar => 2) obj2 = MongoidProject.create(:bar => 2)
@ability.can :read, :mongoid_projects @ability.can :read, MongoidProject
@ability.cannot :read, :mongoid_projects, :bar => 2 @ability.cannot :read, MongoidProject, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should == [obj] MongoidProject.accessible_by(@ability, :read).entries.should == [obj]
end end
it "combines the rules" do it "should combine the rules" do
obj = MongoidProject.create(:bar => 1) obj = MongoidProject.create(:bar => 1)
obj2 = MongoidProject.create(:bar => 2) obj2 = MongoidProject.create(:bar => 2)
obj3 = MongoidProject.create(:bar => 3) obj3 = MongoidProject.create(:bar => 3)
@ability.can :read, :mongoid_projects, :bar => 1 @ability.can :read, MongoidProject, :bar => 1
@ability.can :read, :mongoid_projects, :bar => 2 @ability.can :read, MongoidProject, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2] MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2]
end end
it "does not allow to fetch records when ability with just block present" do it "should not allow to fetch records when ability with just block present" do
@ability.can :read, :mongoid_projects do @ability.can :read, MongoidProject do
false false
end end
lambda { lambda {

View File

@@ -5,51 +5,48 @@ require "ostruct" # for OpenStruct below
describe CanCan::Rule do describe CanCan::Rule do
before(:each) do before(:each) do
@conditions = {} @conditions = {}
@rule = CanCan::Rule.new(true, :read, :integers, @conditions) @rule = CanCan::Rule.new(true, :read, Integer, @conditions, nil)
end end
it "returns no association joins if none exist" do it "should return no association joins if none exist" do
@rule.associations_hash.should == {} @rule.associations_hash.should == {}
end end
it "returns no association for joins if just attributes" do it "should return no association for joins if just attributes" do
@conditions[:foo] = :bar @conditions[:foo] = :bar
@rule.associations_hash.should == {} @rule.associations_hash.should == {}
end end
it "returns single association for joins" do it "should return single association for joins" do
@conditions[:foo] = {:bar => 1} @conditions[:foo] = {:bar => 1}
@rule.associations_hash.should == {:foo => {}} @rule.associations_hash.should == {:foo => {}}
end end
it "returns multiple associations for joins" do it "should return multiple associations for joins" do
@conditions[:foo] = {:bar => 1} @conditions[:foo] = {:bar => 1}
@conditions[:test] = {1 => 2} @conditions[:test] = {1 => 2}
@rule.associations_hash.should == {:foo => {}, :test => {}} @rule.associations_hash.should == {:foo => {}, :test => {}}
end end
it "returns nested associations for joins" do it "should return nested associations for joins" do
@conditions[:foo] = {:bar => {1 => 2}} @conditions[:foo] = {:bar => {1 => 2}}
@rule.associations_hash.should == {:foo => {:bar => {}}} @rule.associations_hash.should == {:foo => {:bar => {}}}
end end
it "returns no association joins if conditions is nil" do it "should return no association joins if conditions is nil" do
rule = CanCan::Rule.new(true, :read, :integers) rule = CanCan::Rule.new(true, :read, Integer, nil, nil)
rule.associations_hash.should == {} rule.associations_hash.should == {}
end 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 it "should not be mergeable if conditions are not simple hashes" do
meta_where = OpenStruct.new(:name => 'metawhere', :column => 'test') meta_where = OpenStruct.new(:name => 'metawhere', :column => 'test')
@conditions[meta_where] = :bar @conditions[meta_where] = :bar
@rule.should be_unmergeable @rule.should be_unmergeable
end end
it "should be mergeable if conditions is an empty hash" do
@conditions = {}
@rule.should_not be_unmergeable
end
end end

2
spec/spec.opts Normal file
View File

@@ -0,0 +1,2 @@
--color
--backtrace

View File

@@ -1,27 +1,35 @@
require 'rubygems' require 'rubygems'
require 'bundler/setup' require 'bundler/setup'
require "sqlite3" Bundler.require(:default)
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 'active_support/all'
require 'matchers' require 'matchers'
require 'cancan'
require 'cancan/matchers' require 'cancan/matchers'
RSpec.configure do |config| RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true config.treat_symbols_as_metadata_keys_with_true_values = true
config.filter_run :focus => true config.filter_run :focus => true
config.run_all_when_everything_filtered = 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 end
class Ability class Ability
@@ -31,19 +39,34 @@ class Ability
end end
end end
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:") class Category < SuperModel::Base
class Category < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.boolean :visible
end
has_many :projects has_many :projects
end end
class Project < ActiveRecord::Base module Sub
connection.create_table(table_name) do |t| class Project < SuperModel::Base
t.integer :category_id
t.string :name
end
belongs_to :category 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 end