195 Commits
1.5.1 ... 2.0

Author SHA1 Message Date
Ryan Bates
e6bf4c8d12 Merge pull request #673 from juggler/rules_order
Changes rules order in mongoid specs. Fixes #672
2013-02-21 17:51:25 -08:00
Ryan Bates
68ea78b6e9 Merge pull request #754 from Serabe/new_authorization_bug
Solves problem when authorizing new action.
2013-02-21 17:48:41 -08:00
Sergio Arbeo
1f7e4c8b6b Solves problem when authorizing new action.
Given two models Category and Projects. A Category has_many
projects and Project belongs_to a category. Furthermore,
projects are shallow nested resources in a category.

Let's say that a user can edit certain category's projects
(and only one category can be edited by each user [1]), this is
expressed with the following line in Ability model:

can :new, :projects, category_id: user.category_id

Given the old implementation, we get that any user can 'new'
(though not 'create') a project in any category:

```ruby
def assign_attributes(resource)
  resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
  initial_attributes.each do |attr_name, value|
    resource.send("#{attr_name}=", value)
  end
  resource
end
```

In this case, category_id in project would get overwritten
inside the initial_attributes loop and authorization would pass.
I consider this a buggy behaviour.

[1] User belongs_to a category, and a Category has many
users. On the other hand, there might be users without
any category.

Conflicts:
	spec/cancan/controller_resource_spec.rb
2012-10-04 20:29:28 +02:00
Ryan Bates
f1cebde51a Merge pull request #751 from mculp/2.0
fixes #750 - load hooks return ActiveRecord::Model in Rails 4, use Concern
2012-09-29 11:00:44 -07:00
Matt Culpepper
9550154b09 load hooks return ActiveRecord::Model in Rails 4, use Concern 2012-09-27 23:18:45 -05:00
Max Prokopiev
17043ca61c Fix mongoid example according to ability precedence. Closes #672 2012-07-05 19:04:15 +04:00
Ryan Bates
aed9f26e56 Merge pull request #668 from bukalapak/2.0
Fix namespace split
2012-07-02 13:48:00 -07:00
Nugroho Herucahyono
6c1828acb6 fix namespace split, so we can use / for namespace 2012-06-29 18:29:08 +07:00
Ryan Bates
6886aecb9a bringing up to date with master branch 2012-06-26 17:10:01 -07:00
Ryan Bates
de000fdec7 tests passing with Rails 3.2.6 2012-06-26 15:13:35 -07:00
Ryan Bates
1e89b31bad releasing 1.6.8 2012-06-25 16:17:32 -07:00
Ryan Bates
5f1be25419 preparing for 1.6.8 2012-06-19 12:56:51 -07:00
Ryan Bates
6d7bce78fc updating changelog 2012-06-19 11:58:59 -07:00
Ryan Bates
a0200166cc removing project status section from readme since contributors are now kind enough to keep tabs on the issue tracker 2012-06-19 11:37:45 -07:00
Ryan Bates
112a995327 clearing leftover whitespace 2012-06-19 11:36:59 -07:00
Ryan Bates
944804183e load ostruct for OpenStruct used in spec 2012-06-19 11:35:58 -07:00
Ryan Bates
2b89dbbdfa Merge pull request #653 from andhapp/fix-pull-request-640
Init attributes in InheritedResources controller w/ specs
2012-06-19 10:53:22 -07:00
Ryan Bates
aff8ca60e4 Merge pull request #650 from andhapp/fix-pull-request-486
Fixes Nested Resource Loading
2012-06-19 10:50:29 -07:00
Ryan Bates
33e33c584e Merge pull request #618 from spatil/master
Check for defined ActionController::Base instead ActionController
2012-06-19 10:41:14 -07:00
Anuj Dutta
a1254ca1c6 Fix pull request 640. For some reason github didn't allow a clean merge althought there weren't any conflicts. Fix it so that it's easier to just merge via the UI. 2012-06-19 00:13:19 +01:00
Mike Pack
88aba4664a Refactor out attribute assignment 2012-06-19 00:08:27 +01:00
Mike Pack
b965f5bab4 Add specs for resource attributes.
Remove inconsistent line breaks.
2012-06-19 00:08:27 +01:00
mccraig mccraig of the clan mccraig
c2c0b86c3a initialise attributes after a resource is created by an InheritedResources controller 2012-06-19 00:08:26 +01:00
Mark Sim
d5baed6281 Fixes Nested Resource Loading 2012-06-18 06:52:32 +01:00
Ryan Bates
76d465ae13 Merge pull request #635 from ollym/2.0
Named resources were not loading correctly in 2.0
2012-06-11 09:56:41 -07:00
Ryan Bates
7bf683d8f4 Merge pull request #645 from andhapp/issue-644
Allow users to specify a mix of can and cannot rule for mongoid
2012-06-11 09:52:38 -07:00
Anuj Dutta
da663aaed1 Fix for issue-644 to allow users to specify a mix of can and cannot rules with mongo. 2012-06-10 22:54:45 +01:00
Oliver Morgan
354e34b8ab Fixed bug where parent resources were being regarded as children 2012-06-04 17:44:33 +01:00
Oliver Morgan
245b83f6b4 Classify causes plural model names to be incorrectly renamed
Some model names will be renamed incorrectly e.g. 'business'. It should
be the responsibility of the user to make sure they use a name that
directly corresponds to the model name. The only filtering performed
should be camelize.
2012-05-31 10:45:55 +01:00
Oliver Morgan
78cbcf1db9 Named resources were not being loaded correctly. Fixes #633 2012-05-30 12:39:10 +01:00
Ryan Bates
80a8c39a93 Merge pull request #632 from andhapp/fix-issue-327
Fix to handle MetaWhere and non-MetaWhere conditions correctly.
2012-05-29 10:04:18 -07:00
Ryan Bates
b3f9ffe93b Merge pull request #625 from rogercampos/merging
Adding Ability#merge
2012-05-28 11:02:51 -07:00
Anuj Dutta
c27ead5b9f Fix to handle MetaWhere and non-MetaWhere conditions correctly. 2012-05-26 18:00:50 +01:00
Ryan Bates
0c21831b4d Merge pull request #619 from derekprior/namespace-fix
Updated: port fix for namespaced params from 2.0 back to 1.6
2012-05-14 09:24:25 -07:00
Chris Gunther
b347c7b78c port fix for namespaced params from 2.0 back to 1.6 2012-05-14 10:52:29 -04:00
Ryan Bates
1cdd7b3c18 Merge pull request #509 from moffff/master
Fix 'spec/spec_helper.rb:20: uninitialized constant WithModel (NameError)'
2012-05-11 08:59:41 -07:00
Ryan Bates
7f4f469e58 Merge pull request #492 from soopa/master
Fix "uninitialized constant CanCan::Rule::ModelAdapters"
2012-05-11 08:51:50 -07:00
Ryan Bates
ccd24ab30f fixing Ruby versions running on travis.yml 2012-05-11 08:43:49 -07:00
Ryan Bates
4986de8b3e Merge pull request #570 from bsodmike/bsodmike-2.0
Cancan 2.0 fix for issue #565; fixes namespaced non-db/model backed resources authorization
2012-05-11 08:18:03 -07:00
Gimi Liang
14e1f5cad4 Merge pull request #535 from manuelmeurer/patch-2
Fixed a small typo
2012-05-11 08:17:55 -07:00
Gimi Liang
8e46ccad8c Merge pull request #616 from NickClark/rails_2_3_readme_clarification
Clarify readme for rails 2.3 users
2012-05-11 08:12:22 -07:00
Michael de Silva
0e8c7ca01f cancan 2.0 fix for issue #565; test to properly authorize resource for namespaced controller 2012-05-11 12:00:46 +03:00
Michael de Silva
48ed6f9353 cancan 2.0 fix for issue #565; fixes namespaced non-db/model backed resources authorization 2012-05-11 11:59:00 +03:00
Nicholas Clark
0bbe2e1802 Clarify readme for rails 2.3 users 2012-05-10 19:03:51 -04:00
Ryan Bates
10cbfbb923 adding a .rbenv-version file 2012-05-10 14:24:13 -07:00
Ryan Bates
a8a85f13a3 Merge pull request #541 from icrowley/master
Fixed bug with params for actions that build new instances with namespaced models
2012-05-10 13:51:45 -07:00
Ryan Bates
c1f7181336 Merge pull request #505 from nertzy/update_with_model
Use latest with_model gem
2012-05-10 11:45:27 -07:00
Ryan Bates
70515de8c1 Merge pull request #556 from mauriciozaffari/master
Pass forward :if and :unless options to the before filter.
2012-05-10 11:00:32 -07:00
Ryan Bates
b73bd062a8 Merge pull request #564 from flop/master
False positives on multiple nested abilities definitions
2012-05-10 10:59:08 -07:00
Ryan Bates
d1176245e6 Merge pull request #559 from plentz/patch-1
Adding travis-ci badge
2012-05-10 10:47:13 -07:00
Ryan Bates
78e1a17258 Merge pull request #587 from route/patch-1
Just add singleton to description of authorize_resource
2012-05-10 09:27:03 -07:00
Ryan Bates
6e8bc851be Merge pull request #607 from Mixbook/master
Added support for value to be Enumerable
2012-05-10 09:25:59 -07:00
Aryk Grosz
65bbf0e354 Add check for Enumerable as condition value 2012-04-23 00:51:55 -07:00
Ryan Bates
167d3832fc getting data_mapper and mongoid specs passwing with latest versions 2012-04-22 17:01:10 -07:00
Ryan Bates
88cd11ba02 upgrading specs to use Rails 3.2.3 2012-04-22 16:13:04 -07:00
Ryan Bates
1ff1b70de4 disabling MetaWhere feature and making Acitve Record fixture that is always loaded 2012-04-22 16:06:59 -07:00
Ryan Bates
8c72ab40a4 removing .rvmrc, no need for a gemset with Bundler 2012-04-22 15:44:45 -07:00
Ryan Bates
ec36137aa6 changing should spec wording 2012-04-22 15:15:09 -07:00
Ryan Bates
b37f2d083e switching to Rspec stubbing/mocking - no more RR 2012-04-22 14:59:32 -07:00
Ryan Bates
aed37cda03 adding project status message to readme 2012-04-17 14:00:30 -07:00
Shailesh Patil
51702e0f7b checked for ActionContoller::Base instead of just ActionContoller 2012-04-03 15:44:41 +05:30
Dmitriy Vorotilin
f166b5945a Just add singleton to description of authorize_resource 2012-03-23 01:34:04 +04:00
Florent Piteau
ba01349eb0 Don't remove key-value from the subject hash we might want to use it again. 2012-02-29 15:49:19 +01:00
Diego Plentz
37a42e3666 adding travis-ci badge 2012-02-14 23:41:18 -02:00
Mauricio Zaffari
83e2dcebd0 Pass forward :if and :unless options to the before filter. i.e:
load_and_authorize_resource :if => condition == true
2012-02-02 21:06:38 -02:00
Dmitry Afanasyev
baadcb923b Fixed bug with params for actions that build new instances with namespaced models 2012-01-06 01:21:11 +04:00
Manuel Meurer
e65f9bd4fd Fixed typo 2011-12-25 19:55:35 +01:00
Artem Kornienko
f18f53c9ce Fixed problem with 'with_model' gem in DataMapper tests and Mongoid tests. 2011-11-09 18:38:19 +02:00
Grant Hutchins
98312940e4 Use latest with_model gem
Now with_model clears the association class cache
between specs, which fixes a test pollution
problem.
2011-11-02 22:37:37 -04:00
Roger Campos
7797b37c7e Adding Ability#merge 2011-10-31 14:08:50 +01:00
soopa
80ceaf8e17 fix uninitialized constant warning in CanCan::Rule#model_adapter 2011-10-14 20:06:43 -05:00
Ryan Bates
9eebeb2155 releasing 1.6.7 with nested resource fix 2011-10-04 15:04:28 -07:00
Ryan Bates
67a3038628 quick fix to get nested resources working again - closes #482 2011-10-04 15:02:59 -07:00
Ryan Bates
26b40f2b49 releasing version 1.6.6 2011-09-28 17:00:32 -07:00
Ryan Bates
98326394f0 removing jruby/rubinius from travis.yml for now until I figure out why they aren't passing 2011-09-28 16:24:03 -07:00
Patrick Morgan
b3fc5add34 Correct "return cant jump across threads" error when using check_authorization() 2011-09-28 16:05:38 -07:00
Ryan Bates
c94de4ab18 include namespace in params when creating/updating resource - closes #349 2011-09-28 16:00:46 -07:00
Ryan Bates
6de9e4675a consider specificity when finding relevant rules so generic rules will not override specific ones - closes #321 2011-09-28 15:34:08 -07:00
Ryan Bates
1fb2c0160c ignore cannot clause with attributes when not checking for with attributes - closes #406 2011-09-28 14:48:26 -07:00
Jan Vlnas
aa83fee7a4 Add failing example of cannot for attribute, corresponds to #406 2011-09-28 14:11:43 -07:00
Ryan Bates
eafd6cfcde fixing spec for new id_param option 2011-09-28 13:50:46 -07:00
Ryan Bates
67c93619c1 Merge branch 'master' into 2.0 2011-09-28 13:45:49 -07:00
Ryan Bates
610d7e3ec4 Merge pull request #425 from skhisma/master
Allow custom IDs to be specified when calling load_resource
2011-09-28 13:44:19 -07:00
Ryan Bates
092b510a2f fixing namespace controller resource spec 2011-09-28 13:43:34 -07:00
Ryan Bates
2160183e86 Merge branch 'master' into 2.0 2011-09-28 13:35:52 -07:00
Ryan Bates
cfb801ed8d Merge pull request #424 from whilefalse/master
Support for namespaced models and engines
2011-09-28 13:34:30 -07:00
Ryan Bates
0edd310c41 Merge pull request #476 from kirkconnell/with_model-version-fix
Specs fail when running in a freshly installed environment.
2011-09-28 11:57:17 -07:00
Ryan Bates
86063e4846 merging 1.6 additions into 2.0 branch 2011-09-28 11:39:26 -07:00
Ryan Bates
0442634d5a fixing ability generator 2011-09-28 11:18:12 -07:00
Ryan Bates
6c1d685f2c include tests with cancan:ability generator - closes #350 2011-09-28 11:02:18 -07:00
Ryan Bates
6ef2c44f57 fixing model comparison spec, I believe this bug is caused by recent version of with_model 2011-09-28 10:08:21 -07:00
kirkconnell
5ab7dea2f0 use version 1.x of with_model to avoid errors in class comparisons 2011-09-20 18:43:39 -06:00
Ryan Bates
916f97fbf3 updating Rails gem dev dependency 2011-07-23 19:53:18 -07:00
Ryan Bates
2be3f98e11 removing 1.9.2 from .travis.yml 2011-07-23 19:33:43 -07:00
Ryan Bates
0343f8f0b5 Merge pull request #427 from bai/master
Add .travis.yml for building cancan on a lovely Travis CI service
2011-07-23 19:31:45 -07:00
Ryan Bates
987dce0dc2 Merge pull request #421 from amc-projects/master
Compatibility fix for rspec-instafail and rspec1
2011-07-23 14:46:37 -07:00
Ryan Bates
e0492f5d75 Merge pull request #426 from manuelmeurer/patch-1
Fixed documentation for skip_load_resource and skip_authorize_resource.
2011-07-23 13:31:03 -07:00
beawesomeinstead
0fc67e4d56 These lines are defaults, should be fine this way 2011-07-21 17:02:12 +03:00
beawesomeinstead
d24ef454e0 Run rake instead of rake test 2011-07-21 15:48:20 +03:00
Manuel Meurer
e561532bab Fixed typos. 2011-07-21 05:12:09 -07:00
beawesomeinstead
1ab4e2d48c The first try to make cancan pass on Travis CI 2011-07-21 14:51:47 +03:00
Geoff Parsons
7937a282a3 :id_param option to load_resource allows specification of the param name to find members 2011-07-20 13:42:24 -04:00
Steven Anderson
d35419fa4c Added the needed camelize to recent patch for engines and namespaced models 2011-07-20 10:03:50 +01:00
Steven Anderson
6c497b8dd9 Added support for engines and namespaced models. 2011-07-20 09:31:53 +01:00
Michael MacDonald
3b33b36de9 Compatibility fix for using cancan with rspec-instafail and rspec1 2011-07-19 10:27:08 +10:00
Ryan Bates
600a3e16a5 Merge pull request #410 from psanford/improve_datamapper_loading
Load datamapper class methods via append_extensions.
2011-07-01 13:01:32 -07:00
psanford
ccd7a94d40 Load datamapper class methods via append_extensions.
This relaxes the previous requirement that cancan has to be loaded
before any models are. append_extensions will apply to all
previously loaded models as well as ones loaded after.
2011-07-01 12:48:20 -07:00
Ryan Bates
8f815c422a Merge pull request #409 from nhocki/patch-1
Make CanCan Default Message a translatable text.
2011-07-01 11:44:23 -07:00
Nicolás Hock Isaza
1c3e61725f Change the i18n default name to :"unauthorized.default" 2011-07-01 13:10:20 -05:00
Nicolás Hock Isaza
71f60bc4ac Adding tests for i18n translation for default messages 2011-06-30 18:16:47 -05:00
Nicolás Hock Isaza
596ad235a9 Make CanCan Default Message a translatable text. Default to the one you had. 2011-06-30 13:22:49 -07:00
Ryan Bates
b8ff2dbc6a load member through method instead of instance variable to improve decent_exposure support 2011-06-13 15:16:08 -07:00
Ryan Bates
613ab1c1ab delegating ControllerResource find to model adapter, uses 'get' for DataMapper - closes #373 2011-05-21 13:57:17 -07:00
Ryan Bates
c031f82dd2 allow :find_by option to be full find method name - closes #335 2011-05-19 23:37:36 -04:00
Ryan Bates
f6c2054f7e set resource attributes in update action and authorize after set - closes #141 2011-05-19 17:12:30 -04:00
Ryan Bates
a29e31606b changing the interface for ControllerResource load/authorize so they can be intertwined 2011-05-19 16:38:33 -04:00
Ryan Bates
e24d5d146b merging master into 2.0 2011-05-19 16:01:06 -04:00
Ryan Bates
6a01427317 releasing 1.6.5 2011-05-18 13:24:14 -04:00
Ryan Bates
843fe89c63 pass action and subject through AccessDenied exception when :through isn't found - closes #366 2011-05-18 12:58:02 -04:00
Ryan Bates
74c9d582b2 Merge pull request #363 from rahearn/mongoid-conditions-empty
Fixes bug in mongoid_adapter with empty conditions hash
2011-05-17 10:22:19 -07:00
Ryan Bates
4e4c5a9a7f adding current_ability to helper methods - closes #361 2011-05-17 13:21:11 -04:00
Ryan Bates
dde88c92b7 allow :through option to work with private controller methods - closes #360 2011-05-17 13:18:31 -04:00
Ryan Bates
cb9777be5f ensure Mongoid::Document is defined before loading Mongoid adapter - closes #359 2011-05-17 13:16:33 -04:00
Ryan Bates
5a64d94d09 updating version in gemspec to alpha 2011-05-16 18:02:15 -04:00
Ryan Ahearn
0882450232 Processes can rules only if no empty conditions rules are present
1) remove all empty conditions hashes from the rules, they are included
 in the records through `@model_class.all`
2) only process can rules if the new and old rules lists are the same
  length (meaning there were no empty conditions hashes)
3) always process cannot rules
2011-05-12 09:24:38 -04:00
Ryan Ahearn
ad62d60b20 Fixes bug in mongoid_adapter with empty conditions hash
* adds mongoid query that matches every record when
rule.conditions.empty? is true
2011-05-10 11:52:29 -04:00
Ryan Bates
ff13a82dda Merge pull request #355 from emmanuel/issue/245.
DataMapper adapter improvements
2011-05-02 13:52:11 -07:00
Emmanuel Gomez
16bdb8d42e Return empty set early if no can rules are present.
Thanks dkubb!
2011-04-29 12:04:19 -07:00
Emmanuel Gomez
d6851debd4 Fix pending spec for DataMapper adapter. 2011-04-29 00:46:38 -07:00
Emmanuel Gomez
6d39b0ae07 Use dkubb's suggestion for evaluating conditions against a Resource. 2011-04-29 00:31:27 -07:00
Ryan Bates
a6af47d213 Merged pull request #352 from cardagin/topic/mongoid-adapter-enhancements.
Augments Mongoid adapter by handling case where attribute is an array
2011-04-27 09:40:28 -07:00
John Feminella
17c52a7983 Augments Mongoid adapter by handling case where attribute is an array 2011-04-27 09:54:37 -04:00
Ryan Bates
18c1007d3f Merged pull request #343 from rahearn/mongoid-scope.
Adds ability to use Scope query with Mongoid
2011-04-25 09:19:53 -07:00
Ryan Bates
63865cc7d8 allow SQL conditions to be used with a block 2011-04-21 00:46:06 -07:00
Ryan Ahearn
2b6204117f Adds ability to use Scope query with Mongoid
Same limitations apply as with active record
* can not be OR'd with other rules for same ability/controller
2011-04-15 16:58:19 -04:00
Ryan Bates
b1424dfa49 Merge branch 'optional-associations' of https://github.com/socialcast/cancan into socialcast-optional-associations 2011-04-01 15:13:02 -07:00
Mitch Williams
6aaab9e440 Fixed bug where conditions on an optionally associated object would throw exceptions if the associated object was not present at the rule match time. 2011-04-01 13:20:25 -07:00
Florent Piteau
a10243a569 When using an existing scope, it should be merged properly to the class. May fix ryanb/cancan#328 :) 2011-04-01 21:25:19 +02:00
Florent Piteau
81f00f9024 Failling test for nested resources with a scope for conditions 2011-04-01 18:45:33 +02:00
Ryan Bates
7bcfd3d295 releasing 1.6.4 2011-03-29 17:51:15 -07:00
Ryan Bates
e96cf5bea4 fixing mongoid 'or' error - closes #322 2011-03-29 17:49:18 -07:00
Ryan Bates
c6f9abb6ab updating some documentation for CanCan 2.0 2011-03-25 18:48:37 -07:00
Ryan Bates
e5b76210e4 fixing marking fully_authorized on an object instance 2011-03-25 17:05:36 -07:00
Ryan Bates
baa1dacc21 authorize params passed in create and update action 2011-03-25 17:01:12 -07:00
Ryan Bates
f41b39406c don't authorize based on resource name in authorize_resource since this is already handled by enable_authorization 2011-03-25 16:40:20 -07:00
Ryan Bates
27eba72e4b mark index action as fully authorized when fetching records through accessible_by 2011-03-25 16:34:13 -07:00
Ryan Bates
5d68caefd0 removing skipping feature in ControllerResource for now 2011-03-25 16:29:04 -07:00
Ryan Bates
35fbee578f passing block to enable_authorization will be executed when CanCan::Unauthorized exception is raised 2011-03-25 16:08:09 -07:00
Ryan Bates
cf2896f011 renaming AccessDenied exception to Unauthorized 2011-03-25 14:43:36 -07:00
Ryan Bates
bcac159b3e merging with master 2011-03-25 14:32:29 -07:00
Ryan Bates
fb8e9bde57 releasing 1.6.3 2011-03-25 14:28:26 -07:00
Ryan Bates
89e40987d8 make sure ActiveRecord::Relation is defined before checking conditions against it so Rails 2 is supported again - closes #312 2011-03-25 14:26:33 -07:00
Ryan Bates
1ac8099f7a return subject passed to authorize! - closes #314 2011-03-25 14:24:43 -07:00
Ryan Bates
346ca2c74e check authorization is sufficient in an after_filter when doing enable_authorization 2011-03-25 14:11:59 -07:00
Ryan Bates
242e912519 refactoring fully authorized check and catching bug 2011-03-25 13:30:45 -07:00
Ryan Bates
488cc2dfdd require attributes to be checked on create/update action in order to be fully authorized 2011-03-25 13:23:05 -07:00
Ryan Bates
0f3753491d adding fully_authorized? method to Ability to check if conditions are considered in authorize! call 2011-03-25 12:01:53 -07:00
Ryan Bates
85efbdb8d0 adding attributes as 3rd argument to can and can? calls 2011-03-25 11:24:10 -07:00
Ryan Bates
a03d35272b allow strings along with symbols in Ability definition and checking 2011-03-24 11:52:54 -07:00
Ryan Bates
7ee942c334 adding enable_authorization method and deprecating some other controller methods 2011-03-24 11:22:32 -07:00
Ryan Bates
3a825ed0d1 getting all specs passing again 2011-03-23 19:47:34 -07:00
Ryan Bates
98ed39264e modifying Ability to use symbol for subject instead of class, also adding subject aliases 2011-03-23 17:00:33 -07:00
Ryan Bates
5d97cfb236 releasing 1.6.2 2011-03-18 09:44:39 -07:00
Ryan Bates
7688025404 fixing instance loading with :singleton option - closes #310 2011-03-18 09:42:30 -07:00
Ryan Bates
3efa069349 fixing failing MetaWhere spec 2011-03-18 09:14:17 -07:00
Ryan Bates
b0c1646fee releasing 1.6.1 2011-03-15 23:40:14 -07:00
Ryan Bates
3f6cecbfcf use Item.new instead of build_item for singleton resource so it doesn't mess up database - closes #304 2011-03-15 23:37:05 -07:00
Ryan Bates
fdd5ad022d making accessible_by action default to :index and parent action default to :show so we don't check :read action directly - closes #302 2011-03-15 23:00:40 -07:00
Adam Wróbel
3639ca90eb Fixes inherited_resources collection authorization
This reverts e3eab13b86

I don't know what was the idea of that, but it turned out REAL bad.

`collection` sets the collection instance variable. `resource_base` is used all
over CanCan. It's also used inside `load_collection?` which is checked before
`load_collection` is called. That means we actually set the collection instance
variable through inherited_resources (without any authorization whatsoever) before trying to load it through CanCan using `accessible_by`.

    1. def load_resource
    2.  unless skip?(:load)
    3.    if load_instance?
    4.      self.resource_instance ||= load_resource_instance
    5.    elsif load_collection?
    6.      self.collection_instance ||= load_collection
    7.    end
    8.  end
    9. end

`collection_instance` is set on line 5 instead of line 6.
2011-03-16 01:20:35 +01:00
Ryan Bates
efa3ff1c0f releasing 1.6.0 2011-03-10 23:59:13 -08:00
Ryan Bates
9bee4a8d4b adding any/all support for MetaWhere conditions 2011-03-08 23:19:56 -08:00
Ryan Bates
eb2826f135 adding more MetaWhere comparison operators 2011-03-08 22:21:42 -08:00
Ryan Bates
a49269175e Merge branch 'master' into meta_where 2011-03-08 22:05:40 -08:00
Ryan Bates
0de43c445b raise an error when trying to make a rule with both hash conditions and a block - closes #269 2011-03-08 17:20:32 -08:00
Ryan Bates
f9b181af05 allow Active Record scope to be passed as Ability conditions - closes #257 2011-03-08 17:08:26 -08:00
Ryan Bates
80f1ab20fb adding :if and :unless options to check_authorization - closes #284 2011-03-08 16:35:01 -08:00
Ryan Bates
37102fe6f8 load collection resources in custom controller actions with no id param - closes #296 2011-03-08 16:10:40 -08:00
Ryan Bates
ba999970b1 add space in multiword model in I18n unauthorized message - closes #292 2011-03-08 15:56:23 -08:00
Ryan Bates
951d70e057 adding :prepend option to load_and_authorize_resource - closes #290 2011-03-08 15:50:34 -08:00
Ryan Bates
3a07d62782 fixing spec for Inherited Resource parent loading 2011-03-08 15:39:15 -08:00
Ryan Bates
2c2fa306cc Merge branch 'master' of https://github.com/stefanoverna/cancan into stefanoverna-master 2011-03-08 15:33:47 -08:00
Ryan Bates
28a9a0ac07 Merge branch 'inherited_resources_collection_fix' of https://github.com/tanordheim/cancan into tanordheim-inherited_resources_collection_fix 2011-03-08 15:24:14 -08:00
Ryan Bates
bcf2756ad2 simplifying .rvmrc 2011-03-08 15:23:31 -08:00
Ryan Bates
c53ed1e497 raise a NotImplemented exception if it's an unrecognized MetaWhere condition 2011-03-08 11:06:46 -08:00
Ryan Bates
07088a0cdc making it easier to test all MetaWhere conditions 2011-03-08 10:52:49 -08:00
Ryan Bates
ff5aaf543b adding initial MetaWhere support 2011-03-08 10:37:25 -08:00
Ryan Bates
52435e97d9 fixing association conditions when MetaWhere is installed (thanks acmetech) - closes #261 2011-03-08 10:07:36 -08:00
Trond Arve Nordheim
e3eab13b86 Use collection instead of end_of_association_chain in the inherited_resources integration, as per suggested by aq1018 2011-03-08 10:45:34 +01:00
Ryan Bates
79995e4309 adding Lock It Down section to readme 2011-02-22 09:37:53 -08:00
Stefano Verna
8722fbc7a5 Fix for deeply nested resources when using inherited resources 2011-02-17 22:31:17 +01:00
Ryan Bates
3901cbe499 fixing tests for passing action name through to accessible_by call 2011-02-14 10:33:53 -08:00
Ryan Bates
471d54ce01 Merge branch 'pass_action_to_accessible_by' of https://github.com/amw/cancan into amw-pass_action_to_accessible_by 2011-02-14 10:28:59 -08:00
Sam Pohlenz
f23bbe04ef Fix rule check on Hash-like subjects 2011-02-04 16:46:57 +10:30
Adam Wróbel
f1ea21b2a6 Pass action name to accessible_by. 2011-02-03 17:00:46 +01:00
Ryan Bates
b2028c8aa7 moving :alert into redirect_to call in documentation 2011-01-28 09:53:07 -08:00
39 changed files with 2138 additions and 1207 deletions

1
.rbenv-version Normal file
View File

@@ -0,0 +1 @@
1.9.3-p194

1
.rspec
View File

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

23
.rvmrc
View File

@@ -1,23 +0,0 @@
#!/usr/bin/env bash
# adapted from: http://rvm.beginrescueend.com/workflow/rvmrc/
ruby_string="1.8.7"
gemset_name="cancan"
if rvm list strings | grep -q "${ruby_string}" ; then
# Load or create the specified environment
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
\. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
else
rvm --create "${ruby_string}@${gemset_name}"
fi
else
# Notify the user to install the desired interpreter before proceeding.
echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
fi

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
rvm:
- 1.9.3
- 1.9.2
- 1.8.7
- ree
notifications:
recipients:
- ryan@railscasts.com

View File

@@ -1,9 +1,132 @@
1.6.8 (June 25, 2012)
* improved support for namespaced controllers and models
* pass :if and :unless options for load and authorize resource (thanks mauriciozaffari)
* Travis CI badge (thanks plentz)
* adding Ability#merge for combining multiple abilities (thanks rogercampos)
* support for multiple MetaWhere rules (thanks andhapp)
* various fixes for DataMapper, Mongoid, and Inherited Resource integration
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.7...1.6.8]
1.6.7 (October 4, 2011)
* fixing nested resource problem caused by namespace addition - issue #482
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.6...1.6.7]
1.6.6 (September 28, 2011)
* correct "return cant jump across threads" error when using check_authorization (thanks codeprimate) - issues #463, #469
* fixing tests in development by specifying with_model version (thanks kirkconnell) - issue #476
* added travis.yml file for TravisCI support (thanks bai) - issue #427
* better support for namespaced models (thanks whilefalse) - issues #424
* adding :id_param option to load_and_authorize_resource (thanks skhisma) - issue #425
* make default unauthorized message translatable text (thanks nhocki) - issue #409
* improving DataMapper behavior (thanks psanford, maxsum-corin) - issue #410, #373
* allow :find_by option to be full find method name - issue #335
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.5...1.6.6]
1.6.5 (May 18, 2011)
* pass action and subject through AccessDenied exception when :through isn't found - issue #366
* many Mongoid adapter improvements (thanks rahearn, cardagin) - issues #363, #352, #343
* allow :through option to work with private controller methods - issue #360
* ensure Mongoid::Document is defined before loading Mongoid adapter - issue #359
* many DataMapper adapter improvements (thanks emmanuel) - issue #355
* handle checking nil attributes through associations (thanks thatothermitch) - issue #330
* improve scope merging - issue #328
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.4...1.6.5]
1.6.4 (March 29, 2011)
* Fixed mongoid 'or' error - see issue #322
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.3...1.6.4]
1.6.3 (March 25, 2011)
* Make sure ActiveRecord::Relation is defined before checking conditions against it so Rails 2 is supported again - see issue #312
* Return subject passed to authorize! - see issue #314
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.2...1.6.3]
1.6.2 (March 18, 2011)
* Fixed instance loading when :singleton option is used - see issue #310
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.1...1.6.2]
1.6.1 (March 15, 2011)
* Use Item.new instead of build_item for singleton resource so it doesn't effect database - see issue #304
* Made accessible_by action default to :index and parent action default to :show instead of :read - see issue #302
* Reverted Inherited Resources "collection" override since it doesn't seem to be working - see issue #305
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.6.0...1.6.1]
1.6.0 (March 11, 2011)
* Added MetaWhere support - see issue #194 and #261
* Allow Active Record scopes in Ability conditions - see issue #257
* Added :if and :unless options to check_authorization - see issue #284
* Several Inherited Resources fixes (thanks aq1018, tanordheim and stefanoverna)
* Pass action name to accessible_by call when loading a collection (thanks amw)
* Added :prepend option to load_and_authorize_resource to load before other filters - see issue #290
* Fixed spacing issue in I18n message for multi-word model names - see issue #292
* Load resource collection for any action which doesn't have an "id" parameter - see issue #296
* Raise an exception when trying to make a Ability condition with both a hash of conditions and a block - see issue #269
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.5.1...1.6.0]
1.5.1 (January 20, 2011) 1.5.1 (January 20, 2011)
* Fixing deeply nested conditions in Active Record adapter - see issue #246 * Fixing deeply nested conditions in Active Record adapter - see issue #246
* Improving Mongoid support for multiple can and cannot definitions (thanks stellard) - see issue #239 * Improving Mongoid support for multiple can and cannot definitions (thanks stellard) - see issue #239
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.5.0...1.5.1]
1.5.0 (January 11, 2011) 1.5.0 (January 11, 2011)
@@ -25,6 +148,8 @@
* Internal: added .rvmrc to auto-switch to 1.8.7 with gemset - see issue #231 * Internal: added .rvmrc to auto-switch to 1.8.7 with gemset - see issue #231
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.4.1...1.5.0]
1.4.1 (November 12, 2010) 1.4.1 (November 12, 2010)
@@ -38,6 +163,8 @@
* Fix odd behavior when "cache_classes = false" (thanks mphalliday) - see issue #174 * Fix odd behavior when "cache_classes = false" (thanks mphalliday) - see issue #174
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.4.0...1.4.1]
1.4.0 (October 5, 2010) 1.4.0 (October 5, 2010)
@@ -77,11 +204,15 @@
* No longer calling block in +can+ definition when checking on class - see issue #116 * No longer calling block in +can+ definition when checking on class - see issue #116
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.3.4...1.4.0]
1.3.4 (August 31, 2010) 1.3.4 (August 31, 2010)
* Don't stop at +cannot+ with hash conditions when checking class (thanks tamoya) - see issue #131 * Don't stop at +cannot+ with hash conditions when checking class (thanks tamoya) - see issue #131
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.3.3...1.3.4]
1.3.3 (August 20, 2010) 1.3.3 (August 20, 2010)
@@ -89,16 +220,22 @@
* Pluralize nested associations for conditions in accessible_by (thanks mlooney) - see issue #123 * Pluralize nested associations for conditions in accessible_by (thanks mlooney) - see issue #123
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.3.2...1.3.3]
1.3.2 (August 7, 2010) 1.3.2 (August 7, 2010)
* Fixing slice error when passing in custom resource name - see issue #112 * Fixing slice error when passing in custom resource name - see issue #112
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.3.1...1.3.2]
1.3.1 (August 6, 2010) 1.3.1 (August 6, 2010)
* Fixing protected sanitize_sql error - see issue #111 * Fixing protected sanitize_sql error - see issue #111
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.3.0...1.3.1]
1.3.0 (August 6, 2010) 1.3.0 (August 6, 2010)
@@ -126,6 +263,8 @@
* Supporting deeply nested aliases - see issue #98 * Supporting deeply nested aliases - see issue #98
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.2.0...1.3.0]
1.2.0 (July 16, 2010) 1.2.0 (July 16, 2010)
@@ -141,11 +280,15 @@
* Adding joins clause to accessible_by when conditions are across associations * Adding joins clause to accessible_by when conditions are across associations
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.1.1...1.2.0]
1.1.1 (April 17, 2010) 1.1.1 (April 17, 2010)
* Fixing behavior in Rails 3 by properly initializing ResourceAuthorization * Fixing behavior in Rails 3 by properly initializing ResourceAuthorization
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.1...1.1.1]
1.1.0 (April 17, 2010) 1.1.0 (April 17, 2010)
@@ -169,6 +312,8 @@
* Support additional arguments to can? which get passed to the block - see issue #48 * Support additional arguments to can? which get passed to the block - see issue #48
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.0.2...1.1]
1.0.2 (Dec 30, 2009) 1.0.2 (Dec 30, 2009)
@@ -178,6 +323,8 @@
* Adding custom message argument to unauthorized! method (thanks tjwallace) - see issue #18 * Adding custom message argument to unauthorized! method (thanks tjwallace) - see issue #18
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.0.1...1.0.2]
1.0.1 (Dec 14, 2009) 1.0.1 (Dec 14, 2009)
@@ -185,6 +332,8 @@
* Don't fetch parent of nested resource if *_id parameter is missing so it works with shallow nested routes - see issue #14 * Don't fetch parent of nested resource if *_id parameter is missing so it works with shallow nested routes - see issue #14
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.0.0...1.0.1]
1.0.0 (Dec 13, 2009) 1.0.0 (Dec 13, 2009)
@@ -200,6 +349,8 @@
* BACKWARDS INCOMPATIBLE: turning load and authorize resource methods into class methods which set up the before filter so they can accept additional arguments. * BACKWARDS INCOMPATIBLE: turning load and authorize resource methods into class methods which set up the before filter so they can accept additional arguments.
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/0.2.1...1.0.0]
0.2.1 (Nov 26, 2009) 0.2.1 (Nov 26, 2009)
@@ -209,6 +360,8 @@
* support custom objects (usually symbols) in can definition - see issue #8 * support custom objects (usually symbols) in can definition - see issue #8
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/0.2.0...0.2.1]
0.2.0 (Nov 17, 2009) 0.2.0 (Nov 17, 2009)
@@ -220,6 +373,8 @@
* BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to set up abilities - see issue #4 * BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to set up abilities - see issue #4
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/0.1.0...0.2.0]
0.1.0 (Nov 16, 2009) 0.1.0 (Nov 16, 2009)

16
Gemfile
View File

@@ -1,19 +1,3 @@
source "http://rubygems.org" source "http://rubygems.org"
case ENV["MODEL_ADAPTER"]
when nil, "active_record"
gem "sqlite3-ruby", :require => "sqlite3"
gem "activerecord", :require => "active_record"
gem "with_model"
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,101 +1,108 @@
= CanCan = CanCan {<img src="https://secure.travis-ci.org/ryanb/cancan.png" />}[http://travis-ci.org/ryanb/cancan]
Wiki[https://github.com/ryanb/cancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan] This is the branch for CanCan 2.0 which is in very early development. For a stable release please check out the {master branch}[https://github.com/ryanb/cancan]
CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the +Ability+ class) and not duplicated across controllers, views, and database queries. Here are some instructions for setting up CanCan 2.0. Try this out and provide feedback in the {issue tracker}[https://github.com/ryanb/cancan/issues].
== Installation == Setup
In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command. CanCan expects your controllers to have a +current_user+ method. Add some authentication for this (such as Devise[https://github.com/plataformatec/devise], Authlogic[https://github.com/binarylogic/authlogic] or {nifty:authentication}[https://github.com/ryanb/nifty-generators]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/Changing-Defaults] to customize this behavior.
gem "cancan" To install CanCan, add it to your Gemfile and run the `bundle` command.
In <b>Rails 2</b>, add this to your environment.rb file. gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0"
config.gem "cancan" Next generate an Ability class, this is where your permissions will be defined.
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
See {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details. Add authorization by calling {enable_authorization}[https://github.com/ryanb/cancan/wiki/enable_authorization] in your ApplicationController.
=== 2. Check Abilities & Authorization
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 can? :update, @article %>
<%= link_to "Edit", edit_article_path(@article) %>
<% end %>
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.
def show
@article = Article.find(params[:id])
authorize! :read, @article
end
Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.
class ArticlesController < ApplicationController
load_and_authorize_resource
def show
# @article is already loaded and authorized
end
end
See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information.
=== 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 class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception| enable_authorization
flash[:alert] = exception.message end
redirect_to root_url
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.
== Defining Abilities
You grant access to controller actions through the +Ability+ class which was generated above. The +current_user+ is passed in allowing you to define permissions based on user attributes. For example:
if user
can :access, :all
else
can :access, :home
can :create, [:users, :sessions]
end
Here if there is a user he will be able to perform any action on any controller. If someone is not logged in he can only access the home, users, and sessions controllers.
The first argument to +can+ is the action the user can perform. The second argument is the controller name they can perform that action on. You can pass <tt>:access</tt> and <tt>:all</tt> to represent any action and controller respectively.
As shown above, pass an array to either of these will grant permission on each item in the array. It isn't necessary to pass the +new+ action here because CanCan includes some default aliases. See the {Aliases}[https://github.com/ryanb/cancan/wiki/Aliases] page for details.
You can check permissions in any controller or view using the <tt>can?</tt> method.
<% if can? :create, :comments %>
<%= link_to "New Comment", new_comment_path %>
<% end %>
Here the link will only show up the user can create comments.
== Resource Conditions
What if you need to change authorization based on a model's attributes? You can do so by passing a hash of conditions as the last argument to +can+. For example, if you want to only allow one to access projects which he owns you can set the <tt>:user_id</tt>.
can :access, :projects, :user_id => user.id
A block can also be used for complex condition checks just like in CanCan 1, but here it is not necessary.
If you try visiting any of the project pages at this point you will see a <tt>CanCan::InsufficientAuthorizationCheck</tt> exception is raised. This is because the default authorization has no way to check permissions on the <tt>@project</tt> instance. You can check permissions on an object manually using the <tt>authorize!</tt> method.
def edit
@project = Project.find(params[:id])
authorize! :edit, @project
end
However this can get tedious. Instead CanCan provides a +load_and_authorize_resource+ method to load the <tt>@project</tt> instance in every controller action and authorize it.
class ProjectsController < ApplicationController
load_and_authorize_resource
def edit
# @project already loaded here and authorized
end end
end end
See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information. The +index+ (and other collection actions) will load the <tt>@projects</tt> instance which automatically limits the projects the user is allowed to access. This is a scope so you can make further calls to +where+ to limit what is returned from the database.
def index
@projects = @projects.where(:hidden => false)
end
You can check permissions on instances using the <tt>can?</tt> method.
<%= link_to "Edit Project", edit_project_path if can? :update, @project %>
Here it will only show the edit link if the +user_id+ matches.
== Wiki Docs == Resource Attributes
* {Upgrading to 1.5}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.5] 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+.
* {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]
can :update, :projects, [:name, :priority]
== Questions or Problems? 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.
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. 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.
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. can :update, :products
cannot :update, :products, :price, :discontinued => true
You can check permissions on specific attributes to determine what to show in the form.
== Special Thanks <%= f.text_field :name if can? :update, @project, :name %>
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 = "1.5.1" s.version = "2.0.0.alpha"
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,10 +10,16 @@ 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.1.0' s.add_development_dependency "rspec", "~> 2.9.0"
s.add_development_dependency 'rails', '~> 3.0.0' s.add_development_dependency "rails", "~> 3.2.6"
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 "sqlite3", "~> 1.3.5"
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

@@ -10,4 +10,4 @@ require 'cancan/model_adapters/abstract_adapter'
require 'cancan/model_adapters/default_adapter' require 'cancan/model_adapters/default_adapter'
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)

View File

@@ -8,7 +8,7 @@ module CanCan
# #
# def initialize(user) # def initialize(user)
# if user.admin? # if user.admin?
# can :manage, :all # can :access, :all
# else # else
# can :read, :all # can :read, :all
# end # end
@@ -22,21 +22,12 @@ 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, Project # can? :create, :projects
# #
# 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 => Project # can? :create, @category => :projects
#
# 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.
@@ -53,9 +44,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, *extra_args) def can?(action, subject, attribute = nil)
match = relevant_rules_for_match(action, subject).detect do |rule| match = relevant_rules_for_match(action, subject, attribute).detect do |rule|
rule.matches_conditions?(action, subject, extra_args) rule.matches_conditions?(action, subject, attribute)
end end
match ? match.base_behavior : false match ? match.base_behavior : false
end end
@@ -71,22 +62,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, Article # can :update, :articles
# #
# 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], [Article, Comment] # can [:update, :destroy], [:articles, :comments]
# #
# You can pass :all to match any object and :manage to match any action. Here are some examples. # You can pass :all to match any object and :access to match any action. Here are some examples.
# #
# can :manage, :all # can :access, :all
# can :update, :all # can :update, :all
# can :manage, Project # can :access, :projects
# #
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns. # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
# #
# can :read, Project, :active => true, :user_id => user.id # can :read, :projects, :active => true, :user_id => user.id
# #
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions # 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.
@@ -94,7 +85,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, Project do |project| # can :update, :projects do |project|
# project.groups.include?(user.group) # project.groups.include?(user.group)
# end # end
# #
@@ -102,27 +93,21 @@ module CanCan
# will be denied access. The downside to using a block is that it cannot be used to generate # 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.
# #
# You can pass custom objects into this "can" method, this is usually done with a symbol # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol.
# and is useful if a class isn't available to define permissions on.
# #
# can :read, :stats # can :update, :projects, :priority => 3
# can? :read, :stats # => true # can? :update, :projects # => 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, object_class, object| # can do |action, subject, object|
# # check the database and return true/false # # check the database and return true/false
# end # end
# #
def can(action = nil, subject = nil, conditions = nil, &block) def can(*args, &block)
rules << Rule.new(true, action, subject, conditions, block) rules << Rule.new(true, *args, &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".
@@ -133,35 +118,30 @@ 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, Product do |product| # cannot :read, :projects do |product|
# product.invisible? # product.invisible?
# end # end
# #
def cannot(action = nil, subject = nil, conditions = nil, &block) def cannot(*args, &block)
rules << Rule.new(false, action, subject, conditions, block) rules << Rule.new(false, *args, &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, Comment # can :modify, :comments
# #
# Then :modify permission will apply to both :update and :destroy requests. # Then :modify permission will apply to both :update and :destroy requests.
# #
# can? :update, Comment # => true # can? :update, :comments # => true
# can? :destroy, Comment # => true # can? :destroy, :comments # => 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, Comment # can :update, :comments
# can? :modify, Comment # => false # can? :modify, :comments # => 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.
# #
@@ -172,41 +152,69 @@ 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]
aliased_actions[target] ||= [] aliases[:actions][target] ||= []
aliased_actions[target] += args aliases[:actions][target] += args
end end
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key. # Alias one or more subjects into another one.
def aliased_actions #
@aliased_actions ||= default_alias_actions # alias_subject :admins, :moderators, :to => :users
# can :update, :users
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, :admins # => true
# can? :update, :moderators # => true
#
# This only works in one direction. Passing the aliased subject into the "can?" call
# will not work because aliases are meant to generate more generic subjects.
#
# alias_subject :admins, :moderators, :to => :users
# can :update, :admins
# can? :update, :users # => false
#
def alias_subject(*args)
target = args.pop[:to]
aliases[:subjects][target] ||= []
aliases[:subjects][target] += args
end end
# Removes previously aliased actions including the defaults. # Returns a hash of action and subject aliases.
def clear_aliased_actions def aliases
@aliased_actions = {} @aliases ||= default_aliases
end
# Removes previously aliased actions or subjects including the defaults.
def clear_aliases
aliases[: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)) adapter_class.new(model_class, relevant_rules_for_query(action, model_class.to_s.underscore.pluralize.to_sym))
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) && args.last.has_key?(:message) if args.last.kind_of?(Hash)
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 AccessDenied.new(message, action, subject) raise Unauthorized.new(message, action, subject)
elsif sufficient_attribute_check?(action, subject, attribute) && sufficient_condition_check?(action, subject)
fully_authorized!(action, subject)
end end
subject
end end
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.class == Class ? subject : subject.class).to_s.downcase variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""])) message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
message.blank? ? nil : message message.blank? ? nil : message
end end
@@ -227,32 +235,66 @@ 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)
ability.send(:rules).each do |rule|
rules << rule.dup
end
self
end
private private
def unauthorized_message_keys(action, subject) def unauthorized_message_keys(action, subject)
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.pluralize)
[subject, :all].map do |try_subject| [aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject|
[aliases_for_action(action), :manage].flatten.map do |try_action| [aliases_for(:actions, action.to_sym), :access].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_actions(actions) def expand_aliases(type, items)
actions.map do |action| items.map do |item|
aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item
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_actions. # This does the opposite kind of lookup as expand_aliases.
def aliases_for_action(action) def aliases_for(type, action)
results = [action] results = [action]
aliased_actions.each do |aliased_action, actions| aliases[type].each do |aliased_action, actions|
results += aliases_for_action(aliased_action) if actions.include? action results += aliases_for(type, aliased_action) if actions.include? action
end end
results results
end end
@@ -263,15 +305,20 @@ 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) def relevant_rules(action, subject, attribute = nil)
rules.reverse.select do |rule| specificity = 0
rule.expanded_actions = expand_actions(rule.actions) rules.reverse.each_with_object([]) do |rule, relevant_rules|
rule.relevant? action, subject rule.expanded_actions = expand_aliases(:actions, rule.actions)
rule.expanded_subjects = expand_aliases(:subjects, rule.subjects)
if rule.relevant?(action, subject, attribute) && rule.specificity >= specificity
specificity = rule.specificity if rule.base_behavior
relevant_rules << rule
end
end end
end end
def relevant_rules_for_match(action, subject) def relevant_rules_for_match(action, subject, attribute)
relevant_rules(action, subject).each do |rule| relevant_rules(action, subject, attribute).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
@@ -279,18 +326,22 @@ module CanCan
end end
def relevant_rules_for_query(action, subject) def relevant_rules_for_query(action, subject)
relevant_rules(action, subject).each do |rule| relevant_rules(action, subject, nil).each do |rule|
if rule.only_block? 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_alias_actions def default_aliases
{ {
: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_and_authorize_resource, *args) cancan_resource_class.add_before_filter(self, {:load => true, :authorize => true}, *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.
@@ -94,7 +94,7 @@ module CanCan
# [:+find_by+] # [:+find_by+]
# Find using a different attribute other than id. For example. # Find using a different attribute other than id. For example.
# #
# load_resource :find_by => :permalink # will use find_by_permlink!(params[:id]) # load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
# #
# [:+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
@@ -109,8 +109,12 @@ module CanCan
# #
# load_resource :new => :build # load_resource :new => :build
# #
# [:+prepend+]
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
#
def load_resource(*args) def load_resource(*args)
cancan_resource_class.add_before_filter(self, :load_resource, *args) raise ImplementationRemoved, "The load_resource method has been removed, use load_and_authorize_resource instead."
cancan_resource_class.add_before_filter(self, {:load => true}, *args)
end 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.
@@ -148,6 +152,9 @@ module CanCan
# [:+except+] # [:+except+]
# Does not apply before filter to given actions. # Does not apply before filter to given actions.
# #
# [:+singleton+]
# Pass +true+ if this is a singleton resource through a +has_one+ association.
#
# [:+parent+] # [:+parent+]
# True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
# name is given which does not match the controller. # name is given which does not match the controller.
@@ -162,8 +169,12 @@ module CanCan
# [:+through+] # [:+through+]
# Authorize conditions on this parent resource when instance isn't available. # Authorize conditions on this parent resource when instance isn't available.
# #
# [:+prepend+]
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
#
def authorize_resource(*args) def authorize_resource(*args)
cancan_resource_class.add_before_filter(self, :authorize_resource, *args) raise ImplementationRemoved, "The authorize_resource method has been removed, use load_and_authorize_resource instead."
cancan_resource_class.add_before_filter(self, {:authorize => true}, *args)
end 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
@@ -180,7 +191,7 @@ module CanCan
skip_authorize_resource(*args) skip_authorize_resource(*args)
end end
# Skip both the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to # Skip the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
# only do authorization on certain actions. You can pass :only and :except options to specify which actions to # only do authorization on certain actions. You can pass :only and :except options to specify which actions to
# skip the effects on. It will apply to all actions by default. # skip the effects on. It will apply to all actions by default.
# #
@@ -191,12 +202,13 @@ 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
end end
# Skip both the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to # Skip the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
# only do loading on certain actions. You can pass :only and :except options to specify which actions to # only do loading on certain actions. You can pass :only and :except options to specify which actions to
# skip the effects on. It will apply to all actions by default. # skip the effects on. It will apply to all actions by default.
# #
@@ -207,45 +219,55 @@ 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 ensure it performs authorization through +authorized+! or +authorize_resource+ call. # Add this to a controller to automatically perform authorization on every action.
# 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
# check_authorization # enable_authorization
# end # end
# #
# Any arguments are passed to the +after_filter+ it triggers. # 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]
def check_authorization(*args)
self.after_filter(*args) do |controller|
unless controller.instance_variable_defined?(:@_authorized)
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
end
end
end
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
# #
# class HomeController < ApplicationController # If you need to "skip" authorization in a given controller, it is best to enable :+access+ to it in the +Ability+.
# skip_authorization_check :only => :index
# end
# #
# Any arguments are passed to the +before_filter+ it triggers. # Options:
def skip_authorization_check(*args) # [:+only+]
self.before_filter(*args) do |controller| # Only applies to given actions.
controller.instance_variable_set(:@_authorized, true) #
# [:+except+]
# Does not apply to given actions.
#
# [:+if+]
# Supply the name of a controller method to be called. The authorization only takes place if this returns true.
#
# enable_authorization :if => :admin_controller?
#
# [:+unless+]
# Supply the name of a controller method to be called. The authorization only takes place if this returns false.
#
# enable_authorization :unless => :devise_controller?
#
def enable_authorization(options = {}, &block)
before_filter(options.slice(:only, :except)) do |controller|
break if options[:if] && !controller.send(options[:if])
break if options[:unless] && controller.send(options[:unless])
controller.authorize! controller.params[:action], controller.params[:controller]
end
after_filter(options.slice(:only, :except)) do |controller|
break if options[:if] && !controller.send(options[:if])
break if options[:unless] && controller.send(options[:unless])
unless controller.current_ability.fully_authorized? controller.params[:action], controller.params[:controller]
raise CanCan::InsufficientAuthorizationCheck, "Authorization check is not sufficient for this action. This is probably because you have conditions or attributes defined in Ability and are not checking for them in the action. One way to solve this is adding load_and_authorize_resource to this controller."
end end
end end
rescue_from(CanCan::Unauthorized, &block) if block
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
@@ -256,17 +278,25 @@ 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
@_cancan_skipper ||= {:authorize => {}, :load => {}} raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
end end
end end
def self.included(base) def self.included(base)
base.extend ClassMethods base.extend ClassMethods
base.helper_method :can?, :cannot? base.helper_method :can?, :cannot?, :current_ability
end end
# Raises a CanCan::AccessDenied exception if the current_ability cannot # Raises a CanCan::Unauthorized exception if the current_ability cannot
# perform the given action. This is usually called in a controller action or # 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.
# #
@@ -293,13 +323,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::AccessDenied do |exception| # rescue_from CanCan::Unauthorized do |exception|
# flash[:alert] = exception.message # redirect_to root_url, :alert => exception.message
# redirect_to root_url
# end # end
# end # end
# #
# See the CanCan::AccessDenied exception for more details on working with the exception. # See the CanCan::Unauthorized exception for more details on working with the exception.
# #
# See the load_and_authorize_resource method to automatically add the authorize! behavior # See the load_and_authorize_resource method to automatically add the authorize! behavior
# to the default RESTful actions. # to the default RESTful actions.
@@ -308,10 +337,6 @@ 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.
@@ -360,7 +385,7 @@ module CanCan
end end
end end
if defined? ActionController if defined? ActionController::Base
ActionController::Base.class_eval do ActionController::Base.class_eval do
include CanCan::ControllerAdditions include CanCan::ControllerAdditions
end end

View File

@@ -2,11 +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, method, *args) def self.add_before_filter(controller_class, behavior, *args)
options = args.extract_options! options = args.extract_options!.merge(behavior)
resource_name = args.first resource_name = args.first
controller_class.before_filter(options.slice(:only, :except)) do |controller| before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method) 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
end end
end end
@@ -15,29 +16,27 @@ 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 load_and_authorize_resource def process
load_resource if @options[:load]
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
@@ -45,18 +44,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
@@ -64,7 +63,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_resource find_and_update_resource
end end
end end
@@ -73,46 +72,76 @@ module CanCan
end end
def load_collection? def load_collection?
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class) resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, subject_name)
end end
def load_collection def load_collection
resource_base.accessible_by(current_ability) resource_base.accessible_by(current_ability, authorization_action)
end end
def build_resource def build_resource
method_name = @options[:singleton] && resource_base.respond_to?("build_#{name}") ? "build_#{name}" : "new" resource = resource_base.new(resource_params || {})
resource = resource_base.send(method_name, @params[name] || {}) assign_attributes(resource)
initial_attributes.each do |name, value|
resource.send("#{name}=", value)
end end
def assign_attributes(resource)
initial_attributes.each do |attr_name, value|
resource.send("#{attr_name}=", value)
end
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
resource resource
end end
def initial_attributes def initial_attributes
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value| current_ability.attributes_for(@params[:action].to_sym, subject_name).delete_if do |key, value|
@params[name] && @params[name].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] && resource_base.respond_to?(name) if @options[:singleton] && parent_resource.respond_to?(name)
resource_base.send(name) parent_resource.send(name)
else else
@options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param) if @options[:find_by]
if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
resource_base.send("find_by_#{@options[:find_by]}!", id_param)
else
resource_base.send(@options[:find_by], id_param)
end end
else
adapter.find(resource_base, id_param)
end
end
end
def adapter
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
end end
def authorization_action def authorization_action
parent? ? :read : @params[:action].to_sym parent? ? :show : @params[:action].to_sym
end end
def id_param def id_param
if @options[:id_param]
@params[@options[:id_param]]
else
@params[parent? ? :"#{name}_id" : :id] @params[parent? ? :"#{name}_id" : :id]
end end
end
def member_action? def member_action?
!collection_actions.include? @params[:action].to_sym new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
end end
# Returns the class used for this resource. This can be overriden by the :class option. # Returns the class used for this resource. This can be overriden by the :class option.
@@ -121,14 +150,18 @@ module CanCan
def resource_class def resource_class
case @options[:class] case @options[:class]
when false then name.to_sym when false then name.to_sym
when nil then name.to_s.camelize.constantize when nil then namespaced_name.to_s.camelize.constantize
when String then @options[:class].constantize when String then @options[:class].constantize
else @options[:class] else @options[:class]
end end
end end
def resource_class_with_parent def subject_name
parent_resource ? {parent_resource => resource_class} : resource_class resource_class.to_s.underscore.pluralize.to_sym
end
def subject_name_with_parent
parent_resource ? {parent_resource => subject_name} : subject_name
end end
def resource_instance=(instance) def resource_instance=(instance)
@@ -136,7 +169,13 @@ module CanCan
end end
def resource_instance def resource_instance
@controller.instance_variable_get("@#{instance_name}") if load_instance? if load_instance?
if @controller.instance_variable_defined? "@#{instance_name}"
@controller.instance_variable_get("@#{instance_name}")
elsif @controller.respond_to?(instance_name, true)
@controller.send(instance_name)
end
end
end end
def collection_instance=(instance) def collection_instance=(instance)
@@ -154,26 +193,30 @@ module CanCan
def resource_base def resource_base
if @options[:through] if @options[:through]
if parent_resource if parent_resource
@options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize) @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
elsif @options[:shallow] elsif @options[:shallow]
resource_class resource_class
else else
raise AccessDenied # maybe this should be a record not found error instead? raise Unauthorized.new(nil, authorization_action, @params[:controller].to_sym) # maybe this should be a record not found error instead?
end end
else else
resource_class resource_class
end end
end end
def parent_name
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
end
# The object to load this resource through. # The object to load this resource through.
def parent_resource def parent_resource
@options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first parent_name && fetch_parent(parent_name)
end end
def fetch_parent(name) def fetch_parent(name)
if @controller.instance_variable_defined? "@#{name}" if @controller.instance_variable_defined? "@#{name}"
@controller.instance_variable_get("@#{name}") @controller.instance_variable_get("@#{name}")
elsif @controller.respond_to? name elsif @controller.respond_to?(name, true)
@controller.send(name) @controller.send(name)
end end
end end
@@ -186,6 +229,24 @@ module CanCan
@name || name_from_controller @name || name_from_controller
end end
def resource_params
if @options[:class]
@params[@options[:class].to_s.underscore.gsub('/', '_')]
else
@params[namespaced_name.to_s.underscore.gsub("/", "_")]
end
end
def namespace
@params[:controller].split(/::|\//)[0..-2]
end
def namespaced_name
[namespace, name.camelize].join('::').singularize.camelize.constantize
rescue NameError
name
end
def name_from_controller def name_from_controller
@params[:controller].sub("Controller", "").underscore.split('/').last.singularize @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
end end

View File

@@ -11,11 +11,14 @@ 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::AccessDenied.new("Not authorized!", :read, Article) # raise CanCan::Unauthorized.new("Not authorized!", :read, Article)
# #
# The passed message, action, and subject are optional and can later be retrieved when # The passed message, action, and subject are optional and can later be retrieved when
# rescuing from the exception. # rescuing from the exception.
@@ -30,9 +33,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#authorized! for more information on rescuing from this exception # See ControllerAdditions#authorize! for more information on rescuing from this exception
# and customizing the message using I18n. # and customizing the message using I18n.
class AccessDenied < Error class Unauthorized < Error
attr_reader :action, :subject attr_reader :action, :subject
attr_writer :default_message attr_writer :default_message
@@ -40,7 +43,7 @@ module CanCan
@message = message @message = message
@action = action @action = action
@subject = subject @subject = subject
@default_message = "You are not authorized to access this page." @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
end end
def to_s def to_s

View File

@@ -3,9 +3,11 @@ module CanCan
class InheritedResource < ControllerResource # :nodoc: class InheritedResource < ControllerResource # :nodoc:
def load_resource_instance def load_resource_instance
if parent? if parent?
@controller.send :parent @controller.send :association_chain
@controller.instance_variable_get("@#{instance_name}")
elsif new_actions.include? @params[:action].to_sym elsif new_actions.include? @params[:action].to_sym
@controller.send :build_resource resource = @controller.send :build_resource
assign_attributes(resource)
else else
@controller.send :resource @controller.send :resource
end end

View File

@@ -1,5 +1,5 @@
RSpec = Spec unless defined? RSpec # for RSpec 1 compatability rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # for RSpec 1 compatability
RSpec::Matchers.define :be_able_to do |*args| Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
match do |ability| match do |ability|
ability.can?(*args) ability.can?(*args)
end end

View File

@@ -15,6 +15,11 @@ module CanCan
false # override in subclass false # override in subclass
end end
# Override if you need custom find behavior
def self.find(model_class, id)
model_class.find(id)
end
# Used to determine if this model adapter will override the matching behavior for a hash of conditions. # Used to determine if this model adapter will override the matching behavior for a hash of conditions.
# If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash # If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
def self.override_conditions_hash_matching?(subject, conditions) def self.override_conditions_hash_matching?(subject, conditions)
@@ -26,6 +31,17 @@ module CanCan
raise NotImplemented, "This model adapter does not support matching on a conditions hash." raise NotImplemented, "This model adapter does not support matching on a conditions hash."
end end
# Used to determine if this model adapter will override the matching behavior for a specific condition.
# If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
def self.override_condition_matching?(subject, name, value)
false
end
# Override if override_condition_matching? returns true
def self.matches_condition?(subject, name, value)
raise NotImplemented, "This model adapter does not support matching on a specific condition."
end
def initialize(model_class, rules) def initialize(model_class, rules)
@model_class = model_class @model_class = model_class
@rules = rules @rules = rules

View File

@@ -5,6 +5,37 @@ module CanCan
model_class <= ActiveRecord::Base model_class <= ActiveRecord::Base
end end
def self.override_condition_matching?(subject, name, value)
name.kind_of?(MetaWhere::Column) if defined? MetaWhere
end
def self.matches_condition?(subject, name, value)
subject_value = subject.send(name.column)
if name.method.to_s.ends_with? "_any"
value.any? { |v| meta_where_match? subject_value, name.method.to_s.sub("_any", ""), v }
elsif name.method.to_s.ends_with? "_all"
value.all? { |v| meta_where_match? subject_value, name.method.to_s.sub("_all", ""), v }
else
meta_where_match? subject_value, name.method, value
end
end
def self.meta_where_match?(subject_value, method, value)
case method.to_sym
when :eq then subject_value == value
when :not_eq then subject_value != value
when :in then value.include?(subject_value)
when :not_in then !value.include?(subject_value)
when :lt then subject_value < value
when :lteq then subject_value <= value
when :gt then subject_value > value
when :gteq then subject_value >= value
when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true)
when :does_not_match then !meta_where_match?(subject_value, :matches, value)
else raise NotImplemented, "The #{method} MetaWhere condition is not supported."
end
end
# Returns conditions intended to be used inside a database query. Normally you will not call this # Returns conditions intended to be used inside a database query. Normally you will not call this
# method directly, but instead go through ModelAdditions#accessible_by. # method directly, but instead go through ModelAdditions#accessible_by.
# #
@@ -36,7 +67,7 @@ module CanCan
conditions.inject({}) do |result_hash, (name, value)| conditions.inject({}) do |result_hash, (name, value)|
if value.kind_of? Hash if value.kind_of? Hash
association_class = model_class.reflect_on_association(name).class_name.constantize association_class = model_class.reflect_on_association(name).class_name.constantize
name = model_class.reflect_on_association(name).table_name name = model_class.reflect_on_association(name).table_name.to_sym
value = tableized_conditions(value, association_class) value = tableized_conditions(value, association_class)
end end
result_hash[name] = value result_hash[name] = value
@@ -55,8 +86,15 @@ module CanCan
end end
def database_records def database_records
if @model_class.respond_to?(:where) && @model_class.respond_to?(:joins) if override_scope
@model_class.scoped.merge(override_scope)
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
mergeable_conditions = @rules.select {|rule| rule.unmergeable? }.blank?
if mergeable_conditions
@model_class.where(conditions).joins(joins) @model_class.where(conditions).joins(joins)
else
@model_class.where(*(@rules.map(&:conditions))).joins(joins)
end
else else
@model_class.scoped(:conditions => conditions, :joins => joins) @model_class.scoped(:conditions => conditions, :joins => joins)
end end
@@ -64,6 +102,18 @@ module CanCan
private private
def override_scope
conditions = @rules.map(&:conditions).compact
if defined?(ActiveRecord::Relation) && conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
if conditions.size == 1
conditions.first
else
rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
end
end
end
def merge_conditions(sql, conditions_hash, behavior) def merge_conditions(sql, conditions_hash, behavior)
if conditions_hash.blank? if conditions_hash.blank?
behavior ? true_sql : false_sql behavior ? true_sql : false_sql
@@ -115,6 +165,8 @@ 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

@@ -5,29 +5,30 @@ module CanCan
model_class <= DataMapper::Resource model_class <= DataMapper::Resource
end end
def self.find(model_class, id)
model_class.get(id)
end
def self.override_conditions_hash_matching?(subject, conditions) def self.override_conditions_hash_matching?(subject, conditions)
conditions.any? { |k,v| !k.kind_of?(Symbol) } conditions.any? { |k,v| !k.kind_of?(Symbol) }
end end
def self.matches_conditions_hash?(subject, conditions) def self.matches_conditions_hash?(subject, conditions)
subject.class.all(:conditions => conditions).include?(subject) # TODO don't use a database query here for performance and other instances collection = DataMapper::Collection.new(subject.query, [ subject ])
!!collection.first(conditions)
end end
def database_records def database_records
scope = @model_class.all(:conditions => ["0 = 1"]) scope = @model_class.all(:conditions => ["0 = 1"])
conditions.each do |condition| cans, cannots = @rules.partition { |r| r.base_behavior }
scope += @model_class.all(:conditions => condition) return scope if cans.empty?
end # apply unions first, then differences. this mean cannot overrides can
cans.each { |r| scope += @model_class.all(:conditions => r.conditions) }
cannots.each { |r| scope -= @model_class.all(:conditions => r.conditions) }
scope scope
end end
end # class DataMapper
end # module ModelAdapters
end # module CanCan
def conditions DataMapper::Model.append_extensions(CanCan::ModelAdditions::ClassMethods)
@rules.map(&:conditions)
end
end
end
end
DataMapper::Model.class_eval do
include CanCan::ModelAdditions::ClassMethods
end

View File

@@ -6,7 +6,14 @@ module CanCan
end end
def self.override_conditions_hash_matching?(subject, conditions) def self.override_conditions_hash_matching?(subject, conditions)
conditions.any? { |k,v| !k.kind_of?(Symbol) } conditions.any? do |k,v|
key_is_not_symbol = lambda { !k.kind_of?(Symbol) }
subject_value_is_array = lambda do
subject.respond_to?(k) && subject.send(k).is_a?(Array)
end
key_is_not_symbol.call || subject_value_is_array.call
end
end end
def self.matches_conditions_hash?(subject, conditions) def self.matches_conditions_hash?(subject, conditions)
@@ -18,12 +25,21 @@ module CanCan
def database_records def database_records
if @rules.size == 0 if @rules.size == 0
@model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria)
@rules[0].conditions
else else
@rules.inject(@model_class.all) do |records, rule| # we only need to process can rules if
if rule.base_behavior # there are no rules with empty conditions
records.or(rule.conditions) rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior }
process_can_rules = @rules.count == rules.count
rules.inject(@model_class.all) do |records, rule|
if process_can_rules && rule.base_behavior
records.or rule.conditions
elsif !rule.base_behavior
records.excludes rule.conditions
else else
records.excludes(rule.conditions) records
end end
end end
end end

View File

@@ -2,9 +2,11 @@ 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 :read. This # can perform a given action on. The action defaults to :index. This
# is usually called from a controller and passed the +current_ability+. # is usually called from a controller and passed the +current_ability+.
# #
# @articles = Article.accessible_by(current_ability) # @articles = Article.accessible_by(current_ability)
@@ -19,13 +21,9 @@ module CanCan
# @articles = Article.accessible_by(current_ability, :update) # @articles = Article.accessible_by(current_ability, :update)
# #
# Here only the articles which the user can update are returned. # Here only the articles which the user can update are returned.
def accessible_by(ability, action = :read) def accessible_by(ability, action = :index)
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

@@ -3,37 +3,39 @@ module CanCan
# it holds the information about a "can" call made on Ability and provides # it holds the information about a "can" call made on Ability and provides
# 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, :actions, :conditions attr_reader :base_behavior, :subjects, :actions, :conditions
attr_writer :expanded_actions attr_writer :expanded_actions, :expanded_subjects
# The first argument when initializing is the base_behavior which is a true/false # 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, subject, conditions, block) def initialize(base_behavior, action = nil, subject = nil, *extra_args, &block)
@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
@conditions = conditions || {} @attributes = [extra_args.shift].flatten if extra_args.first.kind_of?(Symbol) || extra_args.first.kind_of?(Array) && extra_args.first.first.kind_of?(Symbol)
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if extra_args.first.kind_of?(Hash) && !block.nil?
@conditions = extra_args.first || {}
@block = block @block = block
end end
# Matches both the subject and action, not necessarily the conditions # Matches the subject, action, and given attribute. Conditions are not checked here.
def relevant?(action, subject) def relevant?(action, subject, attribute)
subject = subject.values.first if subject.kind_of? Hash subject = subject.values.first if subject.class == Hash
@match_all || (matches_action?(action) && matches_subject?(subject)) @match_all || (matches_action?(action) && matches_subject?(subject) && matches_attribute?(attribute))
end end
# Matches the block or conditions hash # Matches the block or conditions hash
def matches_conditions?(action, subject, extra_args) def matches_conditions?(action, subject, attribute)
if @match_all if @match_all
call_block_with_all(action, subject, extra_args) call_block_with_all(action, subject, attribute)
elsif @block && !subject_class?(subject) elsif @block && subject_object?(subject)
@block.call(subject, *extra_args) @block.arity == 1 ? @block.call(subject) : @block.call(subject, attribute)
elsif @conditions.kind_of?(Hash) && subject.kind_of?(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_class?(subject) elsif @conditions.kind_of?(Hash) && subject_object?(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.
@@ -42,15 +44,27 @@ module CanCan
end end
def only_block? def only_block?
conditions_empty? && !@block.nil? !conditions? && !@block.nil?
end end
def only_raw_sql? def only_raw_sql?
@block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash) @block.nil? && conditions? && !@conditions.kind_of?(Hash)
end end
def conditions_empty? def attributes?
@conditions == {} || @conditions.nil? @attributes.present?
end
def conditions?
@conditions.present?
end
def instance_conditions?
@block || conditions?
end
def unmergeable?
@conditions.respond_to?(:keys) && (! @conditions.keys.first.kind_of? Symbol)
end end
def associations_hash(conditions = @conditions) def associations_hash(conditions = @conditions)
@@ -69,23 +83,42 @@ 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_class?(subject) def subject_object?(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?(:manage) || @expanded_actions.include?(action) @expanded_actions.include?(:access) || @expanded_actions.include?(action.to_sym)
end end
def matches_subject?(subject) def matches_subject?(subject)
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject) subject = subject_name(subject) if subject_object? subject
@expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
end 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)
@subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) } @expanded_subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
end end
# Checks if the given subject matches the given conditions hash. # Checks if the given subject matches the given conditions hash.
@@ -100,14 +133,17 @@ module CanCan
model_adapter(subject).matches_conditions_hash? subject, conditions model_adapter(subject).matches_conditions_hash? subject, conditions
else else
conditions.all? do |name, value| conditions.all? do |name, value|
if model_adapter(subject).override_condition_matching? subject, name, value
model_adapter(subject).matches_condition? subject, name, value
else
attribute = subject.send(name) attribute = subject.send(name)
if value.kind_of?(Hash) if value.kind_of?(Hash)
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
matches_conditions_hash? attribute, value attribute && matches_conditions_hash?(attribute, value)
end end
elsif value.kind_of?(Array) || value.kind_of?(Range) elsif value.kind_of?(Enumerable)
value.include? attribute value.include? attribute
else else
attribute == value attribute == value
@@ -116,22 +152,27 @@ module CanCan
end end
end end
end end
end
def nested_subject_matches_conditions?(subject_hash) def nested_subject_matches_conditions?(subject_hash)
parent, child = subject_hash.shift parent, child = subject_hash.first
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, extra_args) def call_block_with_all(action, subject, attribute)
if subject.class == Class if subject_object? subject
@block.call(action, subject, nil, *extra_args) @block.call(action, subject_name(subject), subject, attribute)
else else
@block.call(action, subject.class, subject, *extra_args) @block.call(action, subject, nil, attribute)
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)
ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class) CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject)
end end
end end
end end

View File

@@ -1,4 +1,5 @@
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. is in the load path. A test/spec file is also generated depending
on if a spec directory exists.

View File

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

View File

@@ -0,0 +1,16 @@
require "spec_helper"
require "cancan/matchers"
describe Ability do
describe "as guest" do
before(:each) do
@ability = Ability.new(nil)
end
it "can only create a user" do
# Define what a guest can and cannot do
# @ability.should be_able_to(:create, :users)
# @ability.should_not be_able_to(:update, :users)
end
end
end

View File

@@ -0,0 +1,10 @@
require "test_helper"
class AbilityTest < ActiveSupport::TestCase
def guest_can_only_create_user
ability = Ability.new(nil)
# Define what a guest can and cannot do
# assert ability.can?(:create, :users)
# assert ability.cannot?(:update, :users)
end
end

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,37 +6,53 @@ 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)
stub(@controller).params { @params } @controller.stub(:params) { @params }
stub(@controller).current_ability { @ability } @controller.stub(:current_ability) { @ability }
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} } # @controller_class.stub(: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[:action] = "show" @params.merge!(:action => "show", :id => 123)
stub(@controller).resource { :project_resource } @controller.stub(:resource) { :project_resource }
CanCan::InheritedResource.new(@controller).load_resource CanCan::InheritedResource.new(@controller, :load => true).process
@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"
stub(@controller).build_resource { :project_resource } @controller.stub(:build_resource) { :project_resource }
CanCan::InheritedResource.new(@controller).load_resource CanCan::InheritedResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == :project_resource @controller.instance_variable_get(:@project).should == :project_resource
end end
it "index should load through @controller.parent when parent" do it "index should load through @controller.association_chain when parent" do
@params[:action] = "index" @params[:action] = "index"
stub(@controller).parent { :project_resource } @controller.stub(:association_chain) { @controller.instance_variable_set(:@project, :project_resource) }
CanCan::InheritedResource.new(@controller, :parent => true).load_resource CanCan::InheritedResource.new(@controller, :load => true, :parent => true).process
@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"
stub(Project).accessible_by(@ability) { :projects } Project.stub(:accessible_by).with(@ability, :index) { :projects }
stub(@controller).end_of_association_chain { Project } @controller.stub(:end_of_association_chain) { Project }
CanCan::InheritedResource.new(@controller).load_resource CanCan::InheritedResource.new(@controller, :load => true).process
@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
@params[:action] = "new"
@ability.can(:create, :projects, :name => "from conditions")
@controller.stub(:build_resource) { Struct.new(:name).new }
CanCan::InheritedResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).name.should == "from conditions"
end
it "should override initial attributes with params" do
@params.merge!(:action => "new", :project => {:name => "from params"})
@ability.can(:create, :projects, :name => "from conditions")
@controller.stub(:build_resource) { Struct.new(:name).new }
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).name.should == "from params"
end
end 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
mock(object).can?(:read, 123) { true } object.should_receive(:can?).with(: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
mock(object).can?(:read, 123) { false } object.should_receive(:can?).with(: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
mock(object).can?(:read, 123) { true } object.should_receive(:can?).with(: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
mock(object).can?(:read, 123, 456) { false } object.should_receive(:can?).with(: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,44 +1,31 @@
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"
RSpec.configure do |config| class Category
config.extend WithModel
end
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
describe CanCan::ModelAdapters::ActiveRecordAdapter do
with_model :category do
table do |t|
t.boolean "visible"
end
model do
has_many :articles has_many :articles
end end
end
with_model :article do class Article < ActiveRecord::Base
table do |t| connection.create_table(table_name) do |t|
t.boolean "published" t.integer :category_id
t.boolean "secret" t.string :name
t.integer "category_id" t.boolean :published
t.boolean :secret
t.integer :priority
end end
model do
belongs_to :category belongs_to :category
has_many :comments has_many :comments
end end
end
with_model :comment do class Comment < ActiveRecord::Base
table do |t| connection.create_table(table_name) do |t|
t.boolean "spam" t.integer :article_id
t.integer "article_id" t.boolean :spam
end end
model do
belongs_to :article belongs_to :article
end end
end
describe CanCan::ModelAdapters::ActiveRecordAdapter do
before(:each) do before(:each) do
Article.delete_all Article.delete_all
Comment.delete_all Comment.delete_all
@@ -48,33 +35,38 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
@comment_table = Comment.table_name @comment_table = Comment.table_name
end end
it "should be for only active record classes" do it "is for only active record classes" do
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::ActiveRecordAdapter.should_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 "should not fetch any records when no abilities are defined" do it "finds record" do
article = Article.create!
CanCan::ModelAdapters::ActiveRecordAdapter.find(Article, article.id).should == article
end
it "does 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 "should fetch all articles when one can read all" do it "fetches all articles when one can read all" do
@ability.can :read, Article @ability.can :read, :articles
article = Article.create! article = Article.create!
Article.accessible_by(@ability).should == [article] Article.accessible_by(@ability).should == [article]
end end
it "should fetch only the articles that are published" do it "fetches only the articles that are published" do
@ability.can :read, Article, :published => true @ability.can :read, :articles, :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 "should fetch any articles which are published or secret" do it "fetches any articles which are published or secret" do
@ability.can :read, Article, :published => true @ability.can :read, :articles, :published => true
@ability.can :read, Article, :secret => true @ability.can :read, :articles, :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)
@@ -82,9 +74,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 "should fetch only the articles that are published and not secret" do it "fetches only the articles that are published and not secret" do
@ability.can :read, Article, :published => true @ability.can :read, :articles, :published => true
@ability.cannot :read, Article, :secret => true @ability.cannot :read, :articles, :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)
@@ -92,112 +84,195 @@ 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 "should only read comments for articles which are published" do it "only reads comments for articles which are published" do
@ability.can :read, Comment, :article => { :published => true } @ability.can :read, :comments, :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 "should only read comments for visible categories through articles" do it "only reads comments for visible categories through articles" do
@ability.can :read, Comment, :article => { :category => { :visible => true } } pending "does ActiveRecord no longer support a deep nested hash of conditions?"
@ability.can :read, :comments, :article => { :category => { :visible => true } }
comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true))) 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 "should allow conditions in SQL and merge with hash conditions" do it "allows conditions in SQL and merge with hash conditions" do
@ability.can :read, Article, :published => true @ability.can :read, :articles, :published => true
@ability.can :read, Article, ["secret=?", true] @ability.can :read, :articles, ["secret=?", true]
article1 = Article.create!(:published => true, :secret => false) article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true)
article4 = Article.create!(:published => false, :secret => false) article4 = Article.create!(:published => false, :secret => false)
Article.accessible_by(@ability).should == [article1, article2, article3]
end
it "allows a scope for conditions" do
@ability.can :read, :articles, Article.where(:secret => true)
article1 = Article.create!(:secret => true)
article2 = Article.create!(:secret => false)
Article.accessible_by(@ability).should == [article1] Article.accessible_by(@ability).should == [article1]
end end
it "should not allow to fetch records when ability with just block present" do it "fetches only associated records when using with a scope for conditions" do
@ability.can :read, Article do @ability.can :read, :articles, Article.where(:secret => true)
category1 = Category.create!(:visible => false)
category2 = Category.create!(:visible => true)
article1 = Article.create!(:secret => true, :category => category1)
article2 = Article.create!(:secret => true, :category => category2)
# for some reason the objects aren't comparing equally here so it's necessary to compare by id
category1.articles.accessible_by(@ability).map(&:id).should == [article1.id]
end
it "raises an exception when trying to merge scope with other conditions" do
@ability.can :read, :articles, :published => true
@ability.can :read, :articles, Article.where(:secret => true)
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read articles ability.")
end
it "does not allow to fetch records when ability with just block present" do
@ability.can :read, :articles 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 "should not allow to check ability on object against SQL conditions without block" do it "does not allow to check ability on object against SQL conditions without block" do
@ability.can :read, Article, ["secret=?", true] @ability.can :read, :articles, ["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 "should have false conditions if no abilities match" do it "has false conditions if no abilities match" do
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'" @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end end
it "should return false conditions for cannot clause" do it "returns false conditions for cannot clause" do
@ability.cannot :read, Article @ability.cannot :read, :articles
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'" @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end end
it "should return SQL for single `can` definition in front of default `cannot` condition" do it "returns SQL for single `can` definition in front of default `cannot` condition" do
@ability.cannot :read, Article @ability.cannot :read, :articles
@ability.can :read, Article, :published => false, :secret => true @ability.can :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't']) @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
end end
it "should return true condition for single `can` definition in front of default `can` condition" do it "returns true condition for single `can` definition in front of default `can` condition" do
@ability.can :read, Article @ability.can :read, :articles
@ability.can :read, Article, :published => false, :secret => true @ability.can :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'" @ability.model_adapter(Article, :read).conditions.should eq(:secret => true, :published => false)
end end
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do it "returns `false condition` for single `cannot` definition in front of default `cannot` condition" do
@ability.cannot :read, Article @ability.cannot :read, :articles
@ability.cannot :read, Article, :published => false, :secret => true @ability.cannot :read, :articles, :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 "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do it "returns `not (sql)` for single `cannot` definition in front of default `can` condition" do
@ability.can :read, Article @ability.can :read, :articles
@ability.cannot :read, Article, :published => false, :secret => true @ability.cannot :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')]) @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
end end
it "should return appropriate sql conditions in complex case" do it "returns appropriate sql conditions in complex case" do
@ability.can :read, Article @ability.can :read, :articles
@ability.can :manage, Article, :id => 1 @ability.can :access, :articles, :id => 1
@ability.can :update, Article, :published => true @ability.can :update, :articles, :published => true
@ability.cannot :update, Article, :secret => true @ability.cannot :update, :articles, :secret => true
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))] @ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
@ability.model_adapter(Article, :manage).conditions.should == {:id => 1} @ability.model_adapter(Article, :access).conditions.should == {:id => 1}
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'" @ability.model_adapter(Article, :read).conditions.should == {:id => 1} # used to be "t=t" but changed with new specificity rule (issue #321)
end end
it "should not forget conditions when calling with SQL string" do it "does not forget conditions when calling with SQL string" do
@ability.can :read, Article, :published => true @ability.can :read, :articles, :published => true
@ability.can :read, Article, ['secret=?', false] @ability.can :read, :articles, ['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 "should have nil joins if no rules" do it "has nil joins if no rules" do
@ability.model_adapter(Article, :read).joins.should be_nil @ability.model_adapter(Article, :read).joins.should be_nil
end end
it "should have nil joins if no nested hashes specified in conditions" do it "has nil joins if no nested hashes specified in conditions" do
@ability.can :read, Article, :published => false @ability.can :read, :articles, :published => false
@ability.can :read, Article, :secret => true @ability.can :read, :articles, :secret => true
@ability.model_adapter(Article, :read).joins.should be_nil @ability.model_adapter(Article, :read).joins.should be_nil
end end
it "should merge separate joins into a single array" do it "merges separate joins into a single array" do
@ability.can :read, Article, :project => { :blocked => false } @ability.can :read, :articles, :project => { :blocked => false }
@ability.can :read, Article, :company => { :admin => true } @ability.can :read, :articles, :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 "should merge same joins into a single array" do it "merges same joins into a single array" do
@ability.can :read, Article, :project => { :blocked => false } @ability.can :read, :articles, :project => { :blocked => false }
@ability.can :read, Article, :project => { :admin => true } @ability.can :read, :articles, :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
pending
@ability.can :read, :articles, :priority.lt => 2
article1 = Article.create!(:priority => 1)
article2 = Article.create!(:priority => 3)
Article.accessible_by(@ability).should == [article1]
@ability.should be_able_to(:read, article1)
@ability.should_not be_able_to(:read, article2)
end
it "should merge MetaWhere and non-MetaWhere conditions" do
pending
@ability.can :read, Article, :priority.lt => 2
@ability.can :read, Article, :priority => 1
article1 = Article.create!(:priority => 1)
article2 = Article.create!(:priority => 3)
Article.accessible_by(@ability).should == [article1]
@ability.should be_able_to(:read, article1)
@ability.should_not be_able_to(:read, article2)
end
it "matches any MetaWhere condition" do
pending
adapter = CanCan::ModelAdapters::ActiveRecordAdapter
article1 = Article.new(:priority => 1, :name => "Hello World")
adapter.matches_condition?(article1, :priority.eq, 1).should be_true
adapter.matches_condition?(article1, :priority.eq, 2).should be_false
adapter.matches_condition?(article1, :priority.eq_any, [1, 2]).should be_true
adapter.matches_condition?(article1, :priority.eq_any, [2, 3]).should be_false
adapter.matches_condition?(article1, :priority.eq_all, [1, 1]).should be_true
adapter.matches_condition?(article1, :priority.eq_all, [1, 2]).should be_false
adapter.matches_condition?(article1, :priority.ne, 2).should be_true
adapter.matches_condition?(article1, :priority.ne, 1).should be_false
adapter.matches_condition?(article1, :priority.in, [1, 2]).should be_true
adapter.matches_condition?(article1, :priority.in, [2, 3]).should be_false
adapter.matches_condition?(article1, :priority.nin, [2, 3]).should be_true
adapter.matches_condition?(article1, :priority.nin, [1, 2]).should be_false
adapter.matches_condition?(article1, :priority.lt, 2).should be_true
adapter.matches_condition?(article1, :priority.lt, 1).should be_false
adapter.matches_condition?(article1, :priority.lteq, 1).should be_true
adapter.matches_condition?(article1, :priority.lteq, 0).should be_false
adapter.matches_condition?(article1, :priority.gt, 0).should be_true
adapter.matches_condition?(article1, :priority.gt, 1).should be_false
adapter.matches_condition?(article1, :priority.gteq, 1).should be_true
adapter.matches_condition?(article1, :priority.gteq, 2).should be_false
adapter.matches_condition?(article1, :name.like, "%ello worl%").should be_true
adapter.matches_condition?(article1, :name.like, "hello world").should be_true
adapter.matches_condition?(article1, :name.like, "hello%").should be_true
adapter.matches_condition?(article1, :name.like, "h%d").should be_true
adapter.matches_condition?(article1, :name.like, "%helo%").should be_false
adapter.matches_condition?(article1, :name.like, "hello").should be_false
adapter.matches_condition?(article1, :name.like, "hello.world").should be_false
# For some reason this is reporting "The not_matches MetaWhere condition is not supported."
# adapter.matches_condition?(article1, :name.nlike, "%helo%").should be_true
# adapter.matches_condition?(article1, :name.nlike, "%ello worl%").should be_false
end
end end
end end

View File

@@ -3,20 +3,20 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
DataMapper.setup(:default, 'sqlite::memory:') DataMapper.setup(:default, 'sqlite::memory:')
class Article class DataMapperArticle
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, :comments has n, :data_mapper_comments
end end
class Comment class DataMapperComment
include DataMapper::Resource include DataMapper::Resource
property :id, Serial property :id, Serial
property :spam, Boolean, :default => false property :spam, Boolean, :default => false
belongs_to :article belongs_to :data_mapper_article
end end
DataMapper.finalize DataMapper.finalize
@@ -24,87 +24,92 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
describe CanCan::ModelAdapters::DataMapperAdapter do describe CanCan::ModelAdapters::DataMapperAdapter do
before(:each) do before(:each) do
Article.destroy DataMapperArticle.destroy
Comment.destroy DataMapperComment.destroy
@ability = Object.new @ability = Object.new
@ability.extend(CanCan::Ability) @ability.extend(CanCan::Ability)
end end
it "should be for only data mapper classes" do it "is for only data mapper classes" do
CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(Article) CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(DataMapperArticle)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::DataMapperAdapter CanCan::ModelAdapters::AbstractAdapter.adapter_class(DataMapperArticle).should == CanCan::ModelAdapters::DataMapperAdapter
end end
it "should not fetch any records when no abilities are defined" do it "finds record" do
Article.create article = DataMapperArticle.create
Article.accessible_by(@ability).should be_empty CanCan::ModelAdapters::DataMapperAdapter.find(DataMapperArticle, article.id).should == article
end end
it "should fetch all articles when one can read all" do it "does not fetch any records when no abilities are defined" do
@ability.can :read, Article DataMapperArticle.create
article = Article.create DataMapperArticle.accessible_by(@ability).should be_empty
Article.accessible_by(@ability).should == [article]
end end
it "should fetch only the articles that are published" do it "fetches all articles when one can read all" do
@ability.can :read, Article, :published => true @ability.can :read, :data_mapper_articles
article1 = Article.create(:published => true) article = DataMapperArticle.create
article2 = Article.create(:published => false) DataMapperArticle.accessible_by(@ability).should == [article]
Article.accessible_by(@ability).should == [article1]
end end
it "should fetch any articles which are published or secret" do it "fetches only the articles that are published" do
@ability.can :read, Article, :published => true @ability.can :read, :data_mapper_articles, :published => true
@ability.can :read, Article, :secret => true article1 = DataMapperArticle.create(:published => true)
article1 = Article.create(:published => true, :secret => false) article2 = DataMapperArticle.create(:published => false)
article2 = Article.create(:published => true, :secret => true) DataMapperArticle.accessible_by(@ability).should == [article1]
article3 = Article.create(:published => false, :secret => true)
article4 = Article.create(:published => false, :secret => false)
Article.accessible_by(@ability).should == [article1, article2, article3]
end end
it "should fetch only the articles that are published and not secret" do it "fetches any articles which are published or secret" do
@ability.can :read, :data_mapper_articles, :published => true
@ability.can :read, :data_mapper_articles, :secret => true
article1 = DataMapperArticle.create(:published => true, :secret => false)
article2 = DataMapperArticle.create(:published => true, :secret => true)
article3 = DataMapperArticle.create(:published => false, :secret => true)
article4 = DataMapperArticle.create(:published => false, :secret => false)
DataMapperArticle.accessible_by(@ability).should == [article1, article2, article3]
end
it "fetches only the articles that are published and not secret" do
pending "the `cannot` may require some custom SQL, maybe abstract out from Active Record adapter" 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 "should only read comments for articles which are published" do it "only reads comments for articles which are published" do
@ability.can :read, Comment, :article => { :published => true } @ability.can :read, :data_mapper_comments, :data_mapper_article => { :published => true }
comment1 = Comment.create(:article => Article.create!(:published => true)) comment1 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => true))
comment2 = Comment.create(:article => Article.create!(:published => false)) comment2 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => false))
Comment.accessible_by(@ability).should == [comment1] DataMapperComment.accessible_by(@ability).should == [comment1]
end end
it "should allow conditions in SQL and merge with hash conditions" do it "allows conditions in SQL and merge with hash conditions" do
@ability.can :read, Article, :published => true @ability.can :read, :data_mapper_articles, :published => true
@ability.can :read, Article, ["secret=?", true] @ability.can :read, :data_mapper_articles, ["secret=?", true]
article1 = Article.create(:published => true, :secret => false) article1 = DataMapperArticle.create(:published => true, :secret => false)
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 "should match gt comparison" do it "matches gt comparison" do
@ability.can :read, Article, :priority.gt => 3 @ability.can :read, :data_mapper_articles, :priority.gt => 3
article1 = Article.create(:priority => 4) article1 = DataMapperArticle.create(:priority => 4)
article2 = Article.create(:priority => 3) article2 = DataMapperArticle.create(:priority => 3)
Article.accessible_by(@ability).should == [article1] DataMapperArticle.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 "should match gte comparison" do it "matches gte comparison" do
@ability.can :read, Article, :priority.gte => 3 @ability.can :read, :data_mapper_articles, :priority.gte => 3
article1 = Article.create(:priority => 4) article1 = DataMapperArticle.create(:priority => 4)
article2 = Article.create(:priority => 3) article2 = DataMapperArticle.create(:priority => 3)
article3 = Article.create(:priority => 2) article3 = DataMapperArticle.create(:priority => 2)
Article.accessible_by(@ability).should == [article1, article2] DataMapperArticle.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 "should be default for generic classes" do it "is 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,13 +3,11 @@ 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
@@ -30,19 +28,33 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
end.each(&:drop) end.each(&:drop)
end end
it "should be for only Mongoid classes" do it "is for only Mongoid classes" do
CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object) CanCan::ModelAdapters::MongoidAdapter.should_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 "should compare properties on mongoid documents with the conditions hash" do it "finds record" do
project = MongoidProject.create
CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id).should == project
end
it "compares properties on mongoid documents with the conditions hash" do
model = MongoidProject.new model = MongoidProject.new
@ability.can :read, MongoidProject, :id => model.id @ability.can :read, :mongoid_projects, :id => model.id
@ability.should be_able_to(:read, model) @ability.should be_able_to(:read, model)
end end
it "should return [] when no ability is defined so no records are found" do it "is able to read hashes when field is array" do
one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three'])
two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five'])
@ability.can :foo, :mongoid_projects, :numbers => 'one'
@ability.should be_able_to(:foo, one_to_three)
@ability.should_not be_able_to(:foo, two_to_five)
end
it "returns [] 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')
@@ -50,8 +62,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [] MongoidProject.accessible_by(@ability, :read).entries.should == []
end end
it "should return the correct records based on the defined ability" do it "returns the correct records based on the defined ability" do
@ability.can :read, MongoidProject, :title => "Sir" @ability.can :read, :mongoid_projects, :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')
@@ -59,8 +71,29 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [sir] MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end end
it "should return everything when the defined ability is manage all" do it "returns the correct records when a mix of can and cannot rules in defined ability" do
@ability.can :manage, :all pending "TODO figure out why this isn't working"
@ability.can :manage, :mongoid_projects, :title => 'Sir'
@ability.cannot :destroy, :mongoid_projects
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :destroy).entries.should == [sir]
end
it "takes presedence over rule defined without a condition" do
@ability.can :read, :mongoid_projects
@ability.can :read, :mongoid_projects, :title => 'Sir'
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end
it "returns everything when the defined ability is access all" do
@ability.can :access, :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')
@@ -68,11 +101,19 @@ 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
@ability.can :read, :mongoid_projects, MongoidProject.where(:title => 'Sir')
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
it "should handle :field.in" do it "handles :field.in" do
obj = MongoidProject.create(:title => 'Sir') obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"] @ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -85,20 +126,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, MongoidProject, @conditions @ability.can :read, :mongoid_projects, @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, MongoidProject, @conditions @ability.can :read, :mongoid_projects, @conditions
@ability.should be_able_to(:read, obj) @ability.should be_able_to(:read, obj)
end end
end end
it "should handle :field.nin" do it "handles :field.nin" do
obj = MongoidProject.create(:title => 'Sir') obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"] @ability.can :read, :mongoid_projects, :title.nin => ["Lord", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -106,9 +147,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "should handle :field.size" do it "handles :field.size" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave']) obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, MongoidProject, :titles.size => 2 @ability.can :read, :mongoid_projects, :titles.size => 2
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -116,9 +157,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "should handle :field.exists" do it "handles :field.exists" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave']) obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, MongoidProject, :titles.exists => true @ability.can :read, :mongoid_projects, :titles.exists => true
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -126,9 +167,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "should handle :field.gt" do it "handles :field.gt" do
obj = MongoidProject.create(:age => 50) obj = MongoidProject.create(:age => 50)
@ability.can :read, MongoidProject, :age.gt => 45 @ability.can :read, :mongoid_projects, :age.gt => 45
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj] MongoidProject.accessible_by(@ability, :read).should == [obj]
@@ -136,9 +177,9 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
it "should handle instance not saved to database" do it "handles instance not saved to database" do
obj = MongoidProject.new(:title => 'Sir') obj = MongoidProject.new(:title => 'Sir')
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"] @ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true @ability.can?(:read, obj).should == true
# accessible_by only returns saved records # accessible_by only returns saved records
@@ -149,31 +190,31 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
end end
end end
it "should call where with matching ability conditions" do it "calls where with matching ability conditions" do
obj = MongoidProject.create(:foo => {:bar => 1}) obj = MongoidProject.create(:foo => {:bar => 1})
@ability.can :read, MongoidProject, :foo => {:bar => 1} @ability.can :read, :mongoid_projects, :foo => {:bar => 1}
MongoidProject.accessible_by(@ability, :read).entries.first.should == obj MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
end end
it "should exclude from the result if set to cannot" do it "excludes from the result if set to cannot" do
obj = MongoidProject.create(:bar => 1) obj = MongoidProject.create(:bar => 1)
obj2 = MongoidProject.create(:bar => 2) obj2 = MongoidProject.create(:bar => 2)
@ability.can :read, MongoidProject @ability.can :read, :mongoid_projects
@ability.cannot :read, MongoidProject, :bar => 2 @ability.cannot :read, :mongoid_projects, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should == [obj] MongoidProject.accessible_by(@ability, :read).entries.should == [obj]
end end
it "should combine the rules" do it "combines 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, MongoidProject, :bar => 1 @ability.can :read, :mongoid_projects, :bar => 1
@ability.can :read, MongoidProject, :bar => 2 @ability.can :read, :mongoid_projects, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2] MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2]
end end
it "should not allow to fetch records when ability with just block present" do it "does not allow to fetch records when ability with just block present" do
@ability.can :read, MongoidProject do @ability.can :read, :mongoid_projects do
false false
end end
lambda { lambda {

View File

@@ -1,39 +1,55 @@
require "spec_helper" require "spec_helper"
require "ostruct" # for OpenStruct below
# Most of Rule functionality is tested in Ability specs # Most of Rule functionality is tested in Ability specs
describe CanCan::Rule do describe CanCan::Rule do
before(:each) do before(:each) do
@conditions = {} @conditions = {}
@rule = CanCan::Rule.new(true, :read, Integer, @conditions, nil) @rule = CanCan::Rule.new(true, :read, :integers, @conditions)
end end
it "should return no association joins if none exist" do it "returns no association joins if none exist" do
@rule.associations_hash.should == {} @rule.associations_hash.should == {}
end end
it "should return no association for joins if just attributes" do it "returns no association for joins if just attributes" do
@conditions[:foo] = :bar @conditions[:foo] = :bar
@rule.associations_hash.should == {} @rule.associations_hash.should == {}
end end
it "should return single association for joins" do it "returns 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 "should return multiple associations for joins" do it "returns 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 "should return nested associations for joins" do it "returns 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 "should return no association joins if conditions is nil" do it "returns no association joins if conditions is nil" do
rule = CanCan::Rule.new(true, :read, Integer, nil, nil) rule = CanCan::Rule.new(true, :read, :integers)
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
meta_where = OpenStruct.new(:name => 'metawhere', :column => 'test')
@conditions[meta_where] = :bar
@rule.should be_unmergeable
end
end end

View File

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

View File

@@ -1,19 +1,27 @@
require 'rubygems' require 'rubygems'
require 'bundler/setup' require 'bundler/setup'
Bundler.require(:default) require "sqlite3"
require "active_record"
case ENV["MODEL_ADAPTER"]
when "data_mapper"
require "dm-core"
require "dm-sqlite-adapter"
require "dm-migrations"
when "mongoid"
require "mongoid"
end
require 'supermodel' # shouldn't Bundler do this already?
require 'active_support/all' require 'active_support/all'
require 'matchers' require 'matchers'
require 'cancan'
require 'cancan/matchers' require 'cancan/matchers'
RSpec.configure do |config| RSpec.configure do |config|
config.mock_with :rr config.treat_symbols_as_metadata_keys_with_true_values = true
config.before(:each) do config.filter_run :focus => true
Project.delete_all config.run_all_when_everything_filtered = true
Category.delete_all
end
end end
class Ability class Ability
@@ -23,10 +31,19 @@ class Ability
end end
end end
class Category < SuperModel::Base ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
class Category < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.boolean :visible
end
has_many :projects has_many :projects
end end
class Project < SuperModel::Base class Project < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.integer :category_id
t.string :name
end
belongs_to :category belongs_to :category
end end