392 Commits
1.0.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
Ryan Bates
929579f03b releasing 1.5.1 2011-01-20 10:16:01 -08:00
Ryan Bates
f9ad4858f5 handle deeply nested conditions properly in active record adapter - closes #246 2011-01-20 10:12:46 -08:00
Ryan Bates
5c4c179c5a cleaning up mongoid adapter a little 2011-01-19 10:17:21 -08:00
Ryan Bates
78cbea5733 Merge branch 'master' of https://github.com/stellard/cancan into stellard-master 2011-01-19 09:25:08 -08:00
stellard
cff922915e improved test assertion 2011-01-18 21:47:33 +00:00
Ryan Bates
2012311c40 readme improvements 2011-01-18 11:55:46 -08:00
stellard
55c8a5045b added cannot support and multiple can support 2011-01-18 18:28:03 +00:00
stellard
344832d199 updated mongoid 2011-01-18 18:27:53 +00:00
Ryan Bates
52b33589dc changing flash[:error] to flash[:alert] in rdocs - closes #238 2011-01-18 09:19:22 -08:00
Ryan Bates
04522c9bc6 releasing 1.5.0 2011-01-11 00:24:49 -08:00
Ryan Bates
120eafeabd updating readme for 1.5 2011-01-11 00:21:42 -08:00
Ryan Bates
e49190fc21 fixing link in readme 2011-01-08 21:48:19 -08:00
Ryan Bates
39bffe91ab fixing github links in readme 2011-01-08 13:25:45 -08:00
Ryan Bates
3885f469d5 updating changelog 2011-01-08 13:06:00 -08:00
Ryan Bates
e2910a74ee moving parts of the README into wiki pages 2011-01-08 12:36:32 -08:00
Ryan Bates
57327119a8 adding skip load and authorize behavior - closes #164 2011-01-08 12:04:55 -08:00
Ryan Bates
71ceb83ded Merge branch 'bowsersenior-master' 2011-01-07 11:55:35 -08:00
Ryan Bates
045a850565 use gemset in rvmrc (thanks bowsersenior) - closes #231 2011-01-07 11:51:24 -08:00
Mani Tadayon
9a14c706d2 Add MongoidAdapter specs for unsaved instances 2011-01-06 08:09:37 -08:00
Mani Tadayon
12b0bff0b6 Use Mongoid::Matchers#matches? instead of a database query in MongoidAdapter#matches_conditions_hash? 2011-01-05 23:12:59 -08:00
Ryan Bates
bc9ecb226d don't authorize uncountable instance in collection action - closes #193 2011-01-05 13:47:38 -08:00
Ryan Bates
15ca8ade3b improving DataMapper adapter and specs 2011-01-05 13:22:06 -08:00
Ryan Bates
cef6c21232 allow model adapter to override condition hash matching in Rule, also clean up Mongoid adapter and specs 2011-01-04 11:43:41 -08:00
Ryan Bates
685e926d96 moving with_model rspec configuration into Active Record model adapter spec 2011-01-04 10:55:48 -08:00
Ryan Bates
bd9480cb51 removing unused sanitization code in mongoid spec 2011-01-04 10:53:31 -08:00
Mani Tadayon
f759ab7e54 Automatically add accessible_by to Mongoid Documents to match CanCan behavior for ActiveRecord and DataMapper.
Previously, CanCan::ModelAdditions had to be included in each and every Mongoid document separately. Also removed manual include of CanCan::ModelAdditions from Mongoid documents in Mongoid adapter specs.
2011-01-03 12:32:44 -08:00
Ryan Bates
6ccb4dd684 adding spec_all rake task to spec readme 2010-12-30 15:51:57 -08:00
Ryan Bates
70b5f9a613 adding spec_all task for running specs for all model adapters 2010-12-30 15:50:40 -08:00
Ryan Bates
f9f71d679d removing query.rb since it is no longer used 2010-12-30 15:45:22 -08:00
Ryan Bates
fc867c9c0d removing Mongoid::Components hack, tests are passing without it, add tests if this is actually needed 2010-12-30 15:43:36 -08:00
Ryan Bates
ec616ae75b moving accessible_by out into ModelAdditions module 2010-12-30 15:40:53 -08:00
Ryan Bates
f7a494dc51 switching mongoid over to new adapter 2010-12-30 15:06:59 -08:00
Ryan Bates
f5dce44697 switching data mapper to new adapter 2010-12-30 14:53:56 -08:00
Ryan Bates
e8d298c223 removing fake sanitize methods in specs because we're using Active Record now 2010-12-30 14:44:31 -08:00
Ryan Bates
8628aa0038 cleanup whitespace 2010-12-30 14:43:25 -08:00
Ryan Bates
bbb02f7c8f dynamically detect which model adapter to use given a class 2010-12-30 14:42:19 -08:00
Ryan Bates
cc30e838c0 fixing active record adapter behavior and improving specs for it 2010-12-30 00:43:22 -08:00
Ryan Bates
af9e77a79e adding initial active record adapter 2010-12-29 16:24:06 -08:00
Ryan Bates
4c5ba09f4c adding model adapter files in proper location with loading behavior 2010-12-29 15:01:49 -08:00
Ryan Bates
f8760b7149 adding some documentation for running specs with different model adapters 2010-12-29 13:37:37 -08:00
Ryan Bates
2dc2a573f9 move mongoid/dm gems into Gemfile and load dynamically based on MODEL_ADAPTER env variable 2010-12-29 13:22:14 -08:00
Ryan Bates
f9a498d2fc moving model adapter specs into their own directory with MODEL_ADAPTER environment variable for choosing which one to run 2010-12-29 13:15:56 -08:00
Ryan Bates
5183113d2d adding .rvmrc file to switch to Ruby 1.8.7 for development 2010-12-29 13:04:22 -08:00
Nate Mueller
d315e22e7a Add support and tests for datamapper.
This broke some of the mongoid tests and I don't know how to fix them.  Both packages
  define Symbol#in, and when you load them both things don't behave properly.  Hopefully
  someone more versed in mongoid can rewrite the spec to not depend on the Symbol extensions.
2010-12-30 04:58:25 +08:00
Ryan Bates
2d31cbdf60 switching gemspec version to 1.5.0.beta1 to avoid confusion 2010-12-28 10:56:48 -08:00
Mani Tadayon
e14e1edec2 Fix bug with MongoidAdditions throwing a NameError when Mongoid is not defined by always checking if Mongoid is defined before referencing Mongoid-related constants
Also add spec for this bug
2010-12-26 02:17:50 -08:00
Mani Tadayon
ebb8e1bf8b Rename Mongoid collection used in spec and fix description for first spec 2010-12-26 01:37:29 -08:00
Ryan Bates
4339ac6546 improve support for rspec scaffolding (thanks voxik) - closes #176 2010-12-21 14:18:20 -08:00
Ryan Bates
5e1e6e182b allow query.conditions to be called multiple times without losing conditions 2010-12-21 14:01:28 -08:00
Ryan Bates
9b8e84944c adding ability generator - closes #170 2010-12-21 11:38:27 -08:00
Ryan Bates
37c149182c renaming CanDefinition to Rule 2010-12-21 10:41:55 -08:00
Ryan Bates
a6959c0ab2 Merge branch 'master' of https://github.com/bowsersenior/cancan into bowsersenior-master 2010-12-21 10:28:31 -08:00
Mani Tadayon
2ee6908a2a Remove commented-out line from gemspec 2010-11-17 09:52:44 -08:00
Mani Tadayon
84c590e8c7 Add comments clarifying alias_method in MongoidAdditions 2010-11-17 09:52:30 -08:00
Mani Tadayon
5ebca1f9bf Update specs for MongoidAdditions to use rr mocks 2010-11-16 15:47:07 -08:00
Tyler Gannon
f6aaa581ef can? should only go to db if there are mongoid criteria in the conditions.
Easier to just do a simple comparison on the object in memory
than to search the database.  Also this allows method calls
and other attributes that might not be found in the database.
2010-11-15 19:43:54 -08:00
Ryan Bates
872e4cfba8 releasing version 1.4.1 2010-11-12 10:51:29 -08:00
Ryan Bates
787511a208 renaming skip_authorization to skip_authorization_check - closes #169 2010-11-12 10:46:03 -08:00
Ryan Bates
92995d791e adding :through_association option to load_resource (thanks hunterae) - closes #171 2010-11-12 10:42:26 -08:00
Ryan Bates
ebf77ed647 fixing specs due to joins method check in active record additions 2010-11-12 10:31:36 -08:00
Nanda Lopes
9a7c427373 Fix NoMethodError
Raises NoMethodError when using ":singleton => true, :shallow => true" and parent_resource is nil
2010-11-13 02:24:31 +08:00
Ramon Tayag
cf263c105d checks if active record responds to 'joins', so this can work with internuity's quick_scopes gem; added .swp files to git ignore 2010-11-13 02:21:56 +08:00
Michael Halliday
79180de372 This fixes an odd error I was seeing in development mode when cache_classes = false (the default), specifically when loading an object throught the parent in load_and_authorize_resource.
Assume Photo model and User model where user has many photos:

@photo = current_user.photos.find(1) # this returns a photo
@photo1 = Photo.find(1)

@photo.kind_of?(Photo) is not always true for some reason when class_cacheing is false.  Where as @photo1.kind_of?(Photo) always appears to be true.  Of interesting note, in the above example @photo != @photo1 if kind_of? is false.  Very odd.
 
Again, this only appears to be when loading and object through an association.
2010-11-13 02:09:06 +08:00
Mani Tadayon
dbcd93e095 Fix bug with Mongoid document where :manage :all caused accessible_by to return nothing and add specs to test for :manage :all. 2010-10-14 18:21:59 -07:00
Mani Tadayon
d256aeb26e Fix bug with CanDefinition#tableized_conditions being used with Mongoid documents and add more specs for accesible_by with Mongoid. 2010-10-14 17:39:19 -07:00
Mani Tadayon
25bf479f48 Fix accessible_by for Mongoid documents when no ability is defined.
The previous spec that checked for this was not right, since there were no documents in the collection, so every query would return an empty result.
2010-10-13 19:41:30 -07:00
Mani Tadayon
ab82dcbc8f Add support for Mongoid::Criteria Symbol extensions (:age.gt => 10) along with specs. 2010-10-13 15:22:53 -07:00
Mani Tadayon
be74df0548 Add support for Mongoid documents along with basic specs. 2010-10-13 14:41:29 -07:00
Ryan Bates
f901c367fc using supermodel in specs to remove some of the model stubs 2010-10-08 11:46:41 -07:00
Ryan Bates
67cd83dd96 releasing version 1.4.0 2010-10-05 16:25:04 -07:00
Ryan Bates
6c3e87eea9 updating readme and documentation 2010-10-05 16:18:35 -07:00
Ryan Bates
fa766e71ed looks like Bundler automatically requires cancan for the specs 2010-10-05 14:02:01 -07:00
Ryan Bates
b0cec5251c adding a couple things to the changelog 2010-10-05 12:00:50 -07:00
Ryan Bates
18b45d5cfc adding Gemfile, to get specs running just bundle and rake - closes #163 2010-10-05 10:09:37 -07:00
Ryan Bates
8f49f28713 don't stop at cannot definitions when there are no conditions - closes #161 2010-10-04 11:11:14 -07:00
Sokolov Yura
12037d7f43 should not allow to can? when raw sql without block is present 2010-10-05 02:01:48 +08:00
Ryan Bates
1f81b8dd1e use 'send' to access controller current_ability in case it's private 2010-09-23 11:58:55 -07:00
Ryan Bates
3391c5a0d7 adding to changelog 2010-09-21 12:03:16 -07:00
Ryan Bates
c11ffb6fd1 support loading resource :through method along with instance variable - closes #146 2010-09-21 11:42:47 -07:00
Ryan Bates
264e2d2b68 raise AccessDenied error when loading child while parent is nil, pass :shallow => true to bypass 2010-09-21 11:32:56 -07:00
Sokolov Yura
ebef3cc745 consistency addition for ability check on Module 2010-09-21 00:16:36 +08:00
Ryan Bates
cef55c95e7 fetch cancan_resource_class through controller class from ControllerResource 2010-09-16 16:01:58 -07:00
Sokolov Yura
7e0e72276e add specs to sql conditions by not Hash 2010-09-17 06:57:26 +08:00
Sokolov Yura
ff9a91705b add specs for module definitions 2010-09-17 06:57:25 +08:00
Sokolov Yura
a0f73fe0f7 fix error 2010-09-17 06:57:25 +08:00
Sokolov Yura
e2c341b8d3 allow to check ability by common module 2010-09-17 06:57:24 +08:00
Sokolov Yura
f236b1b5a4 resolve issue 149 2010-09-17 06:57:24 +08:00
Juan Manuel Barreneche
0b270f2d0f Use cancan_resource_class for before filters instead of hardcoded ControllerResource class 2010-09-17 06:40:16 +08:00
Juan Manuel Barreneche
961df90ecd Controllers which use 'inherit_resources' instead of Inheritance may have inherited_resource's methods protected 2010-09-17 06:40:15 +08:00
Ryan Bates
4eee637270 adding support for loading through Inherited Resources - closes #23 2010-09-09 16:28:00 -07:00
Ryan Bates
a5ff826e40 fix pluralize error on index action when passing resource name - closes #143 again 2010-09-07 22:10:16 -07:00
Ryan Bates
9d915457af load the collection instance variable on index action - closes #137 2010-09-07 16:25:02 -07:00
Ryan Bates
158c908d3b adding action and subject variables to I18n unauthorized message - closes #142 2010-09-07 15:48:07 -07:00
Ryan Bates
bf9b8ad1a6 filling in some inline documentation for 1.4 2010-09-03 16:19:39 -07:00
Ryan Bates
1af6c6f395 adding check_authorization and skip_authorization controller class methods to ensure authorization is triggered (thanks justinko) - closes #135 2010-09-03 14:38:55 -07:00
Ryan Bates
7c5243321f have params hash override initial attributes when building a new resource 2010-09-03 14:11:44 -07:00
Ryan Bates
721939babd cleaning up some internal specs and names 2010-09-03 14:00:46 -07:00
Ryan Bates
a744377ac9 the new and create actions will now build the resource with attributes based on ability conditions hash - closes #114 2010-09-03 11:53:47 -07:00
Ryan Bates
1be5bf7a57 don't fail if association conditions aren't specified for nested association check 2010-09-03 11:05:58 -07:00
Ryan Bates
c88cb8f459 passing a hash to can? will check permissions on association, this is done automatically in authorize_resource - closes #121 2010-09-03 11:01:55 -07:00
Ryan Bates
737f8acff5 make it clear in readme that alias_action is an instance method - closes #139 2010-09-03 09:30:47 -07:00
Ryan Bates
a5f838a964 use I18n for unauthorization messages - closes #103 2010-09-02 17:01:10 -07:00
Ryan Bates
66314a89f8 support no arguments to 'can' definition which always calls block 2010-09-02 14:46:38 -07:00
Ryan Bates
b1fb179aaf don't pass action into can block with :manage option - closes #129 2010-09-02 14:29:49 -07:00
Ryan Bates
6105edc6a7 skip block when only class is passed to ability check, also don't pass class to block for :all - closes #116 2010-09-02 14:23:00 -07:00
Ryan Bates
04b523eea4 releasing version 1.3.4 2010-08-31 15:46:26 -07:00
Ryan Bates
5a353c1cba don't stop at cannot definition when checking class - closes #131 2010-08-30 15:20:06 -07:00
Ryan Bates
4fe44af45d be more clear about blocks not working with accessible_by - closes #130 2010-08-30 13:40:31 -07:00
Ryan Bates
a10a38c82f releasing version 1.3.3 2010-08-20 16:27:25 -07:00
Ryan Bates
caed4fcee5 use RSpec namespace for matcher - closes #119 2010-08-18 16:22:43 -07:00
Ryan Bates
e893e12260 fixing broken spec and minor improvements to tableized_conditions method 2010-08-18 16:04:08 -07:00
McClain Looney
3d7742ea43 fix for bug 123 2010-08-17 09:33:11 -05:00
Ryan Bates
a566ea0f4f releasing version 1.3.2 which fixes slice error when passing custom resource name 2010-08-07 08:38:35 -07:00
Ryan Bates
333ddf1970 properly pass along resource name without slice error - closes #112 2010-08-07 08:33:31 -07:00
Ryan Bates
cd74267364 releasing version 1.3.1 with sanitize_sql fix 2010-08-06 23:28:51 -07:00
Ryan Bates
f8631dcc93 fixing error on protected sanitize_sql - closes #111 2010-08-06 23:24:04 -07:00
Ryan Bates
7a17586eb3 releasing version 1.3.0 2010-08-06 16:00:52 -07:00
Ryan Bates
13427e18d5 updating readme 2010-08-06 15:54:25 -07:00
Ryan Bates
1dccc0252a removing :resource option from rdocs since it's not longer used 2010-08-06 15:05:24 -07:00
Ryan Bates
75ce2bdefa allow :parent => false option to work in load/authorize resource 2010-08-06 14:26:57 -07:00
Ryan Bates
c9e0f4e3ef renaming :singular resource option to :singleton 2010-08-06 13:06:18 -07:00
Ryan Bates
7d9e710f05 updating changelog 2010-08-06 11:42:30 -07:00
Ryan Bates
236cece3b3 adding :find_by option to load_resource - closes #19 2010-08-06 11:18:54 -07:00
Ryan Bates
84f4c904b7 adding :singular option to support has_one associations in load/authorize resource - closes #93 2010-08-06 11:03:10 -07:00
Ryan Bates
6998e8bdd1 support multiple resources in :through option of load_resource, this makes polymorphic associations possible - closes #73 2010-08-06 10:35:42 -07:00
Ryan Bates
961b8c2477 consider ancestors when matching classes in Ability#can, this way it works with STI - closes #55 2010-08-06 10:06:37 -07:00
Ryan Bates
a157b65fbf adding :instance_name option to load/authorize_resource - closes #44 2010-08-06 09:35:07 -07:00
Ryan Bates
47f0aa597e change how params are passed to ControllerResource and use HashWithIndifferentAccess in tests 2010-08-06 09:24:01 -07:00
Ryan Bates
67b069579e don't pass nil to 'new' call when no params are specified - closes #63 2010-08-05 16:52:37 -07:00
Ryan Bates
156839b73e only use the :read action when authorizing parent resources 2010-08-05 16:24:08 -07:00
Ryan Bates
25a1c553bf adding :through option to replace :nesting option and moving ResourceAuthorization class code into ControllerResource 2010-08-05 16:12:30 -07:00
Ryan Bates
8dee01195d improving inline documentation for Query 2010-07-21 12:16:08 -07:00
Ryan Bates
5eae169d7b mentioning CanCan contributors in README 2010-07-21 12:06:28 -07:00
Ryan Bates
66ff1f2ea3 removing metrics link in readme, farewell Caliper :( 2010-07-21 12:02:27 -07:00
Ryan Bates
18dcf2a121 mention Rails 3 installation in README - closes #56 2010-07-21 11:59:24 -07:00
Ryan Bates
9b26f4d767 fixing specs for older versions of ruby and rspec 2010-07-21 11:56:19 -07:00
Ryan Bates
25637bb33a removing extra white space at end of lines 2010-07-21 11:45:26 -07:00
Ryan Bates
c5737f6d28 adding thanks to changelog 2010-07-20 17:11:05 -07:00
Ryan Bates
1659f21bb3 updating changelong with support for multiple can calls in accessible_by - closes #71 2010-07-20 17:09:46 -07:00
Ryan Bates
ba8cb3cf6d refactoring query.joins 2010-07-20 17:05:17 -07:00
Ryan Bates
e098ddaacd refactoring query.conditions 2010-07-20 16:00:22 -07:00
Ryan Bates
964a4765b1 removing need to pass tableize option around for query conditions 2010-07-20 13:43:43 -07:00
Ryan Bates
a42e067f3b extracting out Query class for generating sql conditions and association joins 2010-07-20 13:20:01 -07:00
Ryan Bates
60848143b7 refactoring can definition matching behavior 2010-07-20 11:04:03 -07:00
Ryan Bates
5d8f04363d merging with master and resolving a couple conflicts 2010-07-19 16:36:01 -07:00
Ryan Bates
cad425989e supporting deeply nested aliases - closes #98 2010-07-19 16:03:09 -07:00
Ryan Bates
1b4377cbf3 releasing version 1.2.0 2010-07-19 09:21:14 -07:00
Yura Sokolov
75eb1917f9 add test for single cannot definition 2010-05-25 14:28:29 +04:00
Yura Sokolov
5fd793090a fix logic error for single cannot condition - it should return no records 2010-05-25 14:09:01 +04:00
Yura Sokolov
ac19422a90 add tests for merging conditions and joins 2010-05-25 14:02:26 +04:00
Yura Sokolov
9c0346b90b can accept array for sql sanitizing in conditions 2010-05-25 14:01:53 +04:00
Yura Sokolov
b473d8827f CanDefinition#only_block? 2010-05-25 12:23:39 +04:00
Yura Sokolov
bcab8d6369 fix error with single cannot condition 2010-05-25 12:14:01 +04:00
Yura Sokolov
dbc1538054 small refactoring: CanDefinition #definitive? #conditions_empty? 2010-05-24 18:15:20 +04:00
Yura Sokolov
46f03013f3 Merge remote branch 'upstream/master'
Conflicts:
	lib/cancan/ability.rb
	lib/cancan/active_record_additions.rb
	lib/cancan/can_definition.rb
	spec/cancan/ability_spec.rb
2010-05-24 15:17:10 +04:00
Ryan Bates
1ade44221a load parent resources for collection actions such 'index' 2010-05-21 15:22:21 -07:00
Ryan Bates
2a3dd85a18 adding :name option to load_and_authorize_resource if it does not match controller - closes #65 2010-05-21 14:20:45 -07:00
Ryan Bates
dfd84a10ed improving inline documentation 2010-05-21 13:41:24 -07:00
John Allison
7543eedd6a fixing issue when using accessible_by with nil can conditions - closes #66 2010-05-20 17:06:10 -07:00
Logan Raarup
605063b974 Make sure conditions on associations are pluralized 2010-05-21 07:31:29 +08:00
Sokolov Yura
7d7d249182 passing throw matching rules with not matching conditions
Main goal is to allow:

cannot :manage, :all
can :read, :all
can :manage, User, :id=>user.id
can :manage, User, :manager_id=>user.id

Signed-off-by: Sokolov Yura <funny.falcon@gmail.com>
2010-05-16 22:13:02 +04:00
Ryan Bates
06296b0a40 support has_many association or arrays in can conditions hash 2010-04-22 17:39:22 -07:00
Ryan Bates
e20081454f adding joins clause to accessible_by when conditions are across associations 2010-04-20 17:02:28 -07:00
Ryan Bates
4da31c0709 can has cheezburger? (thanks Seivan) 2010-04-20 14:24:26 -07:00
Ryan Bates
5aa6252df6 removing unused methods and a bit more refactoring 2010-04-18 00:44:42 -07:00
Ryan Bates
bbbc8a68e0 refactoring much of Ability class into separate CanDefinition class 2010-04-18 00:11:15 -07:00
Ryan Bates
232ecd5b4b releasing 1.1.1 which fixes behavior in Rails 3 by properly initializing ResourceAuthorization 2010-04-17 14:01:20 -07:00
Ryan Bates
e1652ea424 adding admin namespace wiki page link to readme 2010-04-17 13:27:01 -07:00
Ryan Bates
b9995c6147 minor changes to readme 2010-04-17 12:37:32 -07:00
Ryan Bates
ff8c11cfc5 releasing version 1.1, see wiki and changelog for details 2010-04-17 12:06:06 -07:00
Ryan Bates
f1ba76b61b supporting arrays, ranges, and nested hashes in ability conditions 2010-04-17 11:54:27 -07:00
Ryan Bates
283f58ee16 improving readme with links to wiki 2010-04-17 11:45:41 -07:00
Ryan Bates
f46696348e allow access to classes when using hash conditions since you'll generally want to narrow it down with a database query 2010-04-16 15:56:07 -07:00
Ryan Bates
8903feee70 removing unauthorized! in favor of authorize! and including more information in AccessDenied exception - closes #40 2010-04-16 14:54:18 -07:00
Ryan Bates
ecf2818a9e removing apparently unnecessary user attr_accessor in Ability 2010-04-16 08:57:10 -07:00
Ryan Bates
d9f3c8b0ae renaming noun to subject internally 2010-04-16 08:55:36 -07:00
Ryan Bates
240c281061 renaming ActiveRecordAdditions#can method to accessible_by since it flows better and makes more sense 2010-04-15 23:54:45 -07:00
Ryan Bates
ef5900c5b1 adding caching to current_ability class method, if you're overriding this be sure to add caching there too 2010-04-15 23:28:04 -07:00
Ryan Bates
37f482e8d5 default ActiveRecordAdditions#can method action to :read and use 'scoped' if 'where' is not available 2010-04-15 23:18:49 -07:00
Ryan Bates
3c68a911d0 adding can method to Active Record for fetching records matching a specific ability, still needs documentation 2010-04-15 17:04:36 -07:00
Ryan Bates
baeef0b9dd adding conditions behavior to Ability#can and fetch with Ability#conditions - closes #53 2010-04-15 16:50:47 -07:00
Ryan Bates
23a5888fe0 renaming :class option to :resource for load_and_authorize_resource which now supports a symbol for non models - closes #45 2010-04-15 14:14:22 -07:00
Ryan Bates
f2a1695636 properly handle Admin::AbilitiesController in params[:controller] - closes #46 2010-04-15 13:10:12 -07:00
Ryan Bates
6e1e96c85a allow additional arguments for be_able_to matcher, this requires Ruby 1.8.7 or higher to use matcher 2010-04-15 12:04:43 -07:00
David Chelimsky
cf49c5b9de add be_able_to matcher 2010-04-16 02:46:03 +08:00
David Chelimsky
35c4864de4 simplify paths 2010-04-16 02:46:02 +08:00
Ryan Bates
510cf509ee adding documentation for passing additional arguments to can? 2010-04-15 11:28:58 -07:00
Ryan Bates
69f7a65914 support additional arguments to can? which get passed to the block - closes #48 2010-04-15 11:21:44 -07:00
Ryan Bates
f027b2ebb3 use Dir globbing more efficiently in gemspec 2010-04-05 08:22:02 -07:00
Ryan Bates
5d4138f0b2 cleaning up gemspec 2010-04-02 15:25:38 -07:00
Ryan Bates
3b9cdce09a releasing v1.0.2 2009-12-30 21:33:31 -08:00
Ryan Bates
a13f78d6f9 listing additional resources at top of readme page (including metrics and tests) 2009-12-30 20:29:19 -08:00
Ryan Bates
41f67ad6d0 making spec the default rake task so it works at runcoderun.com 2009-12-30 20:22:26 -08:00
Ryan Bates
7d3b4cdbc2 Adding clear_aliased_actions to Ability which removes previously defined actions including defaults 2009-12-30 18:01:40 -08:00
Ryan Bates
f99d506050 Append aliased actions (don't overwrite them) - closes #20 2009-12-30 17:49:49 -08:00
Ryan Bates
ef22de689b adding custom message argument to unauthorized! method - closes #18 2009-12-15 10:53:05 -08:00
Ryan Bates
67416532f4 adding Railscasts episode to documentation 2009-12-14 09:42:51 -08:00
45 changed files with 4064 additions and 817 deletions

4
.gitignore vendored
View File

@@ -1 +1,5 @@
*.swp
**/*.swp
*.gem
Gemfile.lock
.bundle

1
.rbenv-version Normal file
View File

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

2
.rspec Normal file
View File

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

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,339 @@
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)
* 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
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/1.5.0...1.5.1]
1.5.0 (January 11, 2011)
* Added an Ability generator - see issue #170
* Added DataMapper support (thanks natemueller)
* Added Mongoid support (thanks bowsersenior)
* Added skip_load_and_authorize_resource methods to controller class - see issue #164
* Added support for uncountable resources in index action - see issue #193
* Cleaned up README and added spec/README
* Internal: renamed CanDefinition to Rule
* Internal: added a model adapter layer for easily supporting more ORMs
* 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)
* Renaming skip_authorization to skip_authorization_check - see issue #169
* Adding :through_association option to load_resource (thanks hunterae) - see issue #171
* The :shallow option now works with the :singleton option (thanks nandalopes) - see issue #187
* Play nicely with quick_scopes gem (thanks ramontayag) - see issue #183
* 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)
* Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163
* Stop at 'cannot' definition when there are no conditions - see issue #161
* The :through option will now call a method with that name if instance variable doesn't exist - see issue #146
* Adding :shallow option to load_resource to bring back old behavior of fetching a child without a parent
* Raise AccessDenied error when loading a child and parent resource isn't found
* Abilities defined on a module will apply to anything that includes that module - see issue #150 and #152
* Abilities can be defined with a string of SQL in addition to a block so accessible_by works with a block - see issue #150
* Adding better support for InheritedResource - see issue #23
* Loading the collection instance variable (for index action) using accessible_by - see issue #137
* Adding action and subject variables to I18n unauthorized message - closes #142
* Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed (thanks justinko) - see issue #135
* Setting initial attributes based on ability conditions in new/create actions - see issue #114
* Check parent attributes for nested association in index action - see issue #121
* Supporting nesting in can? method using hash - see issue #121
* Adding I18n support for Access Denied messages (thanks EppO) - see issue #103
* Passing no arguments to +can+ definition will pass action, class, and object to block - see issue #129
* Don't pass action to block in +can+ definition when using :+manage+ option - see issue #129
* 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)
* 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)
* Switching to Rspec namespace to remove deprecation warning in Rspec 2 - see issue #119
* 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)
* 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)
* 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)
* Adding :find_by option to load_resource - see issue #19
* Adding :singleton option to load_resource - see issue #93
* Supporting multiple resources in :through option for polymorphic associations - see issue #73
* Supporting Single Table Inheritance for "can" comparisons - see issue #55
* Adding :instance_name option to load/authorize_resource - see issue #44
* Don't pass nil to "new" to keep MongoMapper happy - see issue #63
* Parent resources are now authorized with :read action.
* Changing :resource option in load/authorize_resource back to :class with ability to pass false
* Removing :nested option in favor of :through option with separate load/authorize call
* Moving internal logic from ResourceAuthorization to ControllerResource class
* Supporting multiple "can" and "cannot" calls with accessible_by (thanks funny-falcon) - see issue #71
* 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)
* Load nested parent resources on collection actions such as "index" (thanks dohzya)
* Adding :name option to load_and_authorize_resource if it does not match controller - see issue #65
* Fixing issue when using accessible_by with nil can conditions (thanks jrallison) - see issue #66
* Pluralize table name for belongs_to associations in can conditions hash (thanks logandk) - see issue #62
* Support has_many association or arrays in can conditions hash
* 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)
* 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)
* Supporting arrays, ranges, and nested hashes in ability conditions
* Removing "unauthorized!" method in favor of "authorize!" in controllers
* Adding action, subject and default_message abilities to AccessDenied exception - see issue #40
* Adding caching to current_ability controller method, if you're overriding this be sure to add caching too.
* Adding "accessible_by" method to Active Record for fetching records matching a specific ability
* Adding conditions behavior to Ability#can and fetch with Ability#conditions - see issue #53
* Renaming :class option to :resource for load_and_authorize_resource which now supports a symbol for non models - see issue #45
* Properly handle Admin::AbilitiesController in params[:controller] - see issue #46
* Adding be_able_to RSpec matcher (thanks dchelimsky), requires Ruby 1.8.7 or higher - see issue #54
* 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)
* Adding clear_aliased_actions to Ability which removes previously defined actions including defaults - see issue #20
* Append aliased actions (don't overwrite them) - see issue #20
* 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)
* Adding :class option to load_resource so one can customize which class to use for the model - see issue #17
* 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)
@@ -19,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.
* {see the full list of changes}[https://github.com/ryanb/cancan/compare/0.2.1...1.0.0]
0.2.1 (Nov 26, 2009)
@@ -28,6 +360,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)
@@ -39,6 +373,8 @@
* 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)

3
Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source "http://rubygems.org"
gemspec

View File

@@ -1,4 +1,4 @@
Copyright (c) 2009 Ryan Bates
Copyright (c) 2011 Ryan Bates
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,203 +1,108 @@
= CanCan
= CanCan {<img src="https://secure.travis-ci.org/ryanb/cancan.png" />}[http://travis-ci.org/ryanb/cancan]
This is a simple authorization solution for Ruby on Rails to restrict what a given user is allowed to access in the application. This is completely decoupled from any role based implementation allowing you to define user roles the way you want. All permissions are stored in a single location for convenience.
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]
This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic]) which provides a current_user model.
See the RDocs[http://rdoc.info/projects/ryanb/cancan] and Wiki[http://wiki.github.com/ryanb/cancan] for additional documentation.
== Installation
You can set it up as a gem in your environment.rb file.
config.gem "cancan"
And then install the gem.
sudo rake gems:install
Alternatively you can install it as a Rails plugin.
script/plugin install git://github.com/ryanb/cancan.git
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].
== Getting Started
== Setup
First, define a class called Ability in "models/ability.rb".
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.
class Ability
include CanCan::Ability
To install CanCan, add it to your Gemfile and run the `bundle` command.
def initialize(user)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0"
This is where all permissions will go. See the "Defining Abilities" section below for more information.
Next generate an Ability class, this is where your permissions will be defined.
You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
rails g cancan:ability
<% if can? :update, @article %>
<%= link_to "Edit", edit_article_path(@article) %>
<% end %>
You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
def show
@article = Article.find(params[:id])
unauthorized! if cannot? :read, @article
end
Setting this for every action can be tedious, therefore the load_and_authorize_resource method is also provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it.
class ArticlesController < ApplicationController
load_and_authorize_resource
def show
# @article is already loaded
end
end
If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the ApplicationController.
Add authorization by calling {enable_authorization}[https://github.com/ryanb/cancan/wiki/enable_authorization] in your ApplicationController.
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = "Sorry, you are not allowed to access that page."
redirect_to root_url
end
enable_authorization
end
This will add an authorization check locking down every controller action. If you try visiting a page, a <tt>CanCan::Unauthorized</tt> exception will be raised since you have not granted the user ability to access it.
== Defining Abilities
As shown above, the Ability class is where all user permissions are defined. The user model is passed into the initialize method so you are free to modify the permissions based on the user's attributes. This way CanCan is completely decoupled with how you choose to handle roles.
You grant access to controller actions through the +Ability+ class which was generated above. The +current_user+ is passed in allowing you to define permissions based on user attributes. For example:
The "can" method accepts two arguments, the first one is the action you're setting the permission for, the second one is the class of object you're setting it on.
can :update, Article
You can pass an array for either of these parameters to match any one.
can [:update, :destroy], [Article, Comment]
In this case the user has the ability to update or destroy both articles and comments.
You can pass a block to provide logic based on the article's attributes.
can :update, Article do |article|
article && article.user == user
if user
can :access, :all
else
can :access, :home
can :create, [:users, :sessions]
end
If the block returns true then the user has that :update ability for that article, otherwise he will be denied access. It's possible for the passed in model to be nil if one isn't specified, so be sure to take that into consideration.
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.
You can pass :all to reference every type of object. In this case the object type will be passed into the block as well (just in case object is nil).
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.
can :read, :all do |object_class, object|
object_class != Order
end
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.
Here the user has permission to read all objects except orders.
You can check permissions in any controller or view using the <tt>can?</tt> method.
You can also pass :manage as the action which will match any action. In this case the action is passed to the block.
can :manage, Comment do |action, comment|
action != :destroy
end
Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done.
can :read, :all
cannot :read, Product
== Checking Abilities
Use the "can?" method in the controller or view to check the user's permission for a given action and object.
can? :destroy, @project
You can also pass the class instead of an instance (if you don't have one handy).
<% if can? :create, Project %>
<%= link_to "New Project", new_project_path %>
<% if can? :create, :comments %>
<%= link_to "New Comment", new_comment_path %>
<% end %>
The "cannot?" method is for convenience and performs the opposite check of "can?"
cannot? :destroy, @project
Here the link will only show up the user can create comments.
== Aliasing Actions
== Resource Conditions
You can use the "alias_action" method to alias one or more actions into one.
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>.
alias_action :update, :destroy, :to => :modify
can :modify, Comment
can? :update, Comment # => true
can :access, :projects, :user_id => user.id
The following aliases are added by default for conveniently mapping common controller actions.
A block can also be used for complex condition checks just like in CanCan 1, but here it is not necessary.
alias_action :index, :show, :to => :read
alias_action :new, :to => :create
alias_action :edit, :to => :update
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
== Authorizing Controller Actions
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.
As mentioned in the Getting Started section, you can use the +load_and_authorize_resource+ method in your controller to load the resource into an instance variable and authorize it. If you have a nested resource you can specify that as well.
load_and_authorize_resource :nested => :author
You can also pass an array to the :+nested+ attribute for deep nesting.
If you want to customize the loading behavior on certain actions, you can do so in a before filter.
class BooksController < ApplicationController
before_filter :find_book_by_permalink, :only => :show
class ProjectsController < ApplicationController
load_and_authorize_resource
private
def find_book_by_permalink
@book = Book.find_by_permalink!(params[:id)
def edit
# @project already loaded here and authorized
end
end
Here the @book instance variable is already set so it will not be loaded again for that action. This works for nested resources as well.
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.
== Assumptions & Configuring
CanCan makes two assumptions about your application.
* You have an Ability class which defines the permissions.
* You have a current_user method in the controller which returns the current user model.
You can override these by overriding the "current_ability" method in your ApplicationController.
def current_ability
UserAbility.new(current_account) # instead of Ability.new(current_user)
def index
@projects = @projects.where(:hidden => false)
end
That's it!
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.
== Testing Abilities
== Resource Attributes
It is very easy to test the Ability model since you can call "can?" directly on it as you would in the view or controller.
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+.
def test "user can only destroy projects which he owns"
user = User.new
ability = Ability.new(user)
assert ability.can?(:destroy, Project.new(:user => user))
assert ability.cannot?(:destroy, Project.new)
end
can :update, :projects, [:name, :priority]
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.
== Special Thanks
You can combine this with a hash of conditions. For example, here the user can update all attributes except the price when the product is discontinued.
can :update, :products
cannot :update, :products, :price, :discontinued => true
You can check permissions on specific attributes to determine what to show in the form.
<%= f.text_field :name if can? :update, @project, :name %>
CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Many thanks to the authors and contributors.

View File

@@ -1,11 +1,18 @@
require 'rubygems'
require 'rake'
require 'spec/rake/spectask'
require 'rspec/core/rake_task'
spec_files = Rake::FileList["spec/**/*_spec.rb"]
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = spec_files
t.spec_opts = ["-c"]
desc "Run RSpec"
RSpec::Core::RakeTask.new do |t|
t.verbose = false
end
desc "Run specs for all adapters"
task :spec_all do
%w[active_record data_mapper mongoid].each do |model_adapter|
puts "MODEL_ADAPTER = #{model_adapter}"
system "rake spec MODEL_ADAPTER=#{model_adapter}"
end
end
task :default => :spec

View File

@@ -1,22 +1,26 @@
Gem::Specification.new do |s|
s.name = "cancan"
s.summary = "Simple authorization solution for Rails."
s.description = "Simple authorization solution for Rails which is completely decoupled from the user's roles. All permissions are stored in a single location for convenience."
s.homepage = "http://github.com/ryanb/cancan"
s.version = "1.0.1"
s.date = "2009-12-14"
s.authors = ["Ryan Bates"]
s.version = "2.0.0.alpha"
s.author = "Ryan Bates"
s.email = "ryan@railscasts.com"
s.homepage = "http://github.com/ryanb/cancan"
s.summary = "Simple authorization solution for Rails."
s.description = "Simple authorization solution for Rails which is decoupled from user roles. All permissions are stored in a single location."
s.require_paths = ["lib"]
s.files = Dir["lib/**/*"] + Dir["spec/**/*"] + ["LICENSE", "README.rdoc", "Rakefile", "CHANGELOG.rdoc", "init.rb"]
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc", "LICENSE"]
s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"]
s.require_path = "lib"
s.has_rdoc = true
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "CanCan", "--main", "README.rdoc"]
s.add_development_dependency "rspec", "~> 2.9.0"
s.add_development_dependency "rails", "~> 3.2.6"
s.add_development_dependency "sqlite3", "~> 1.3.5"
s.rubygems_version = "1.3.4"
s.required_rubygems_version = Gem::Requirement.new(">= 1.2")
s.add_development_dependency "dm-core", "~> 1.2.0"
s.add_development_dependency "dm-sqlite-adapter", "~> 1.2.0"
s.add_development_dependency "dm-migrations", "~> 1.2.0"
s.add_development_dependency "mongoid", "~> 2.4.8"
s.add_development_dependency "bson_ext", "~> 1.6.2"
s.rubyforge_project = s.name
s.required_rubygems_version = ">= 1.3.4"
end

View File

@@ -1,10 +1,13 @@
module CanCan
# This error is raised when a user isn't allowed to access a given
# controller action. See ControllerAdditions#unauthorized! for details.
class AccessDenied < StandardError; end
end
require 'cancan/ability'
require 'cancan/rule'
require 'cancan/controller_resource'
require 'cancan/controller_additions'
require 'cancan/model_additions'
require 'cancan/exceptions'
require 'cancan/inherited_resource'
require File.dirname(__FILE__) + '/cancan/ability'
require File.dirname(__FILE__) + '/cancan/controller_resource'
require File.dirname(__FILE__) + '/cancan/resource_authorization'
require File.dirname(__FILE__) + '/cancan/controller_additions'
require 'cancan/model_adapters/abstract_adapter'
require 'cancan/model_adapters/default_adapter'
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)

View File

@@ -8,7 +8,7 @@ module CanCan
#
# def initialize(user)
# if user.admin?
# can :manage, :all
# can :access, :all
# else
# can :read, :all
# end
@@ -16,15 +16,18 @@ module CanCan
# end
#
module Ability
attr_accessor :user
# Use to check the user's permission for a given action and object.
# Check if the user has permission to perform a given action on an object.
#
# can? :destroy, @project
#
# You can also pass the class instead of an instance (if you don't have one handy).
#
# can? :create, Project
# can? :create, :projects
#
# Nested resources can be passed through a hash, this way conditions which are
# dependent upon the association will work when using a class.
#
# can? :create, @category => :projects
#
# Not only can you use the can? method in the controller and view (see ControllerAdditions),
# but you can also call it directly on an ability instance.
@@ -40,16 +43,12 @@ module CanCan
# assert ability.cannot?(:destroy, Project.new)
# end
#
def can?(action, noun)
(@can_definitions || []).reverse.each do |base_behavior, defined_action, defined_noun, defined_block|
defined_actions = expand_actions(defined_action)
defined_nouns = [defined_noun].flatten
if includes_action?(defined_actions, action) && includes_noun?(defined_nouns, noun)
result = can_perform_action?(action, noun, defined_actions, defined_nouns, defined_block)
return base_behavior ? result : !result
# Also see the RSpec Matchers to aid in testing.
def can?(action, subject, attribute = nil)
match = relevant_rules_for_match(action, subject, attribute).detect do |rule|
rule.matches_conditions?(action, subject, attribute)
end
end
false
match ? match.base_behavior : false
end
# Convenience method which works the same as "can?" but returns the opposite value.
@@ -63,52 +62,55 @@ module CanCan
# Defines which abilities are allowed using two arguments. The first one is the action
# you're setting the permission for, the second one is the class of object you're setting it on.
#
# can :update, Article
# can :update, :articles
#
# You can pass an array for either of these parameters to match any one.
# Here the user has the ability to update or destroy both articles and comments.
#
# can [:update, :destroy], [Article, Comment]
# can [:update, :destroy], [:articles, :comments]
#
# In this case the user has the ability to update or destroy both articles and comments.
# You can pass :all to match any object and :access to match any action. Here are some examples.
#
# You can pass a block to provide logic based on the article's attributes.
# can :access, :all
# can :update, :all
# can :access, :projects
#
# can :update, Article do |article|
# article && article.user == user
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
#
# can :read, :projects, :active => true, :user_id => user.id
#
# 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.
#
# If the conditions hash does not give you enough control over defining abilities, you can use a block
# along with any Ruby code you want.
#
# can :update, :projects do |project|
# project.groups.include?(user.group)
# end
#
# If the block returns true then the user has that :update ability for that article, otherwise he
# will be denied access. It's possible for the passed in model to be nil if one isn't specified,
# so be sure to take that into consideration.
# If the block returns true then the user has that :update ability for that project, otherwise he
# will be denied access. The downside to using a block is that it cannot be used to generate
# conditions for database queries.
#
# You can pass :all to reference every type of object. In this case the object type will be passed
# into the block as well (just in case object is nil).
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol.
#
# can :read, :all do |object_class, object|
# object_class != Order
# can :update, :projects, :priority => 3
# can? :update, :projects # => true
#
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
# block will always be executed. This allows you to override the full behavior if the permissions are
# defined in an external source such as the database.
#
# can do |action, subject, object|
# # check the database and return true/false
# end
#
# Here the user has permission to read all objects except orders.
#
# You can also pass :manage as the action which will match any action. In this case the action is
# passed to the block.
#
# can :manage, Comment do |action, comment|
# action != :destroy
# end
#
# You can pass custom objects into this "can" method, this is usually done through a symbol
# and is useful if a class isn't available to define permissions on.
#
# can :read, :stats
# can? :read, :stats # => true
#
def can(action, noun, &block)
@can_definitions ||= []
@can_definitions << [true, action, noun, block]
def can(*args, &block)
rules << Rule.new(true, *args, &block)
end
# Define 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".
#
# can :read, :all
# cannot :read, Comment
@@ -116,36 +118,30 @@ module CanCan
# A block can be passed just like "can", however if the logic is complex it is recommended
# to use the "can" method.
#
# cannot :read, Product do |product|
# cannot :read, :projects do |product|
# product.invisible?
# end
#
def cannot(action, noun, &block)
@can_definitions ||= []
@can_definitions << [false, action, noun, block]
def cannot(*args, &block)
rules << Rule.new(false, *args, &block)
end
# Alias one or more actions into another one.
#
# alias_action :update, :destroy, :to => :modify
# can :modify, Comment
# can :modify, :comments
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, Comment # => true
# can? :destroy, Comment # => true
# can? :update, :comments # => true
# can? :destroy, :comments # => true
#
# This only works in one direction. Passing the aliased action into the "can?" call
# will not work because aliases are meant to generate more generic actions.
#
# alias_action :update, :destroy, :to => :modify
# can :update, Comment
# can? :modify, Comment # => false
#
# Unless that exact alias is used.
#
# can :modify, Comment
# can? :modify, Comment # => true
# can :update, :comments
# can? :modify, :comments # => false
#
# The following aliases are added by default for conveniently mapping common controller actions.
#
@@ -156,51 +152,197 @@ module CanCan
# This way one can use params[:action] in the controller to determine the permission.
def alias_action(*args)
target = args.pop[:to]
aliased_actions[target] = args
aliases[:actions][target] ||= []
aliases[:actions][target] += args
end
# Alias one or more subjects into another one.
#
# alias_subject :admins, :moderators, :to => :users
# can :update, :users
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, :admins # => true
# can? :update, :moderators # => true
#
# This only works in one direction. Passing the aliased subject into the "can?" call
# will not work because aliases are meant to generate more generic subjects.
#
# alias_subject :admins, :moderators, :to => :users
# can :update, :admins
# can? :update, :users # => false
#
def alias_subject(*args)
target = args.pop[:to]
aliases[:subjects][target] ||= []
aliases[:subjects][target] += args
end
# Returns a hash of action and subject aliases.
def aliases
@aliases ||= default_aliases
end
# Removes previously aliased actions or subjects including the defaults.
def clear_aliases
aliases[:actions] = {}
aliases[:subjects] = {}
end
def model_adapter(model_class, action)
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
adapter_class.new(model_class, relevant_rules_for_query(action, model_class.to_s.underscore.pluralize.to_sym))
end
# See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args)
message = nil
if args.last.kind_of?(Hash)
message = args.pop[:message]
end
attribute = args.first
if cannot?(action, subject, *args)
message ||= unauthorized_message(action, subject)
raise Unauthorized.new(message, action, subject)
elsif sufficient_attribute_check?(action, subject, attribute) && sufficient_condition_check?(action, subject)
fully_authorized!(action, subject)
end
subject
end
def unauthorized_message(action, subject)
keys = unauthorized_message_keys(action, subject)
variables = {:action => action.to_s}
variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
message.blank? ? nil : message
end
def attributes_for(action, subject)
attributes = {}
relevant_rules(action, subject).map do |rule|
attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
end
attributes
end
def has_block?(action, subject)
relevant_rules(action, subject).any?(&:only_block?)
end
def has_raw_sql?(action, subject)
relevant_rules(action, subject).any?(&:only_raw_sql?)
end
def has_instance_conditions?(action, subject)
relevant_rules(action, subject).any?(&:instance_conditions?)
end
def has_attributes?(action, subject)
relevant_rules(action, subject).any?(&:attributes?)
end
def fully_authorized?(action, subject)
@fully_authorized ||= []
@fully_authorized.include? [action.to_sym, subject.to_sym]
end
def fully_authorized!(action, subject)
subject = subject.class.to_s.underscore.pluralize.to_sym unless subject.kind_of?(Symbol) || subject.kind_of?(String)
@fully_authorized ||= []
@fully_authorized << [action.to_sym, subject.to_sym]
end
def merge(ability)
ability.send(:rules).each do |rule|
rules << rule.dup
end
self
end
private
def aliased_actions
@aliased_actions ||= default_alias_actions
end
def default_alias_actions
{
:read => [:index, :show],
:create => [:new],
:update => [:edit],
}
end
def expand_actions(actions)
[actions].flatten.map do |action|
if aliased_actions[action]
[action, *aliased_actions[action]]
else
action
def unauthorized_message_keys(action, subject)
subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.pluralize)
[aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject|
[aliases_for(:actions, action.to_sym), :access].flatten.map do |try_action|
:"#{try_action}.#{try_subject}"
end
end.flatten
end
def can_perform_action?(action, noun, defined_actions, defined_nouns, defined_block)
if defined_block.nil?
true
else
block_args = []
block_args << action if defined_actions.include?(:manage)
block_args << (noun.class == Class ? noun : noun.class) if defined_nouns.include?(:all)
block_args << (noun.class == Class ? nil : noun)
return defined_block.call(*block_args)
def sufficient_attribute_check?(action, subject, attribute)
!(%w[create update].include?(action.to_s) && attribute.nil? && has_attributes?(action, subject))
end
def sufficient_condition_check?(action, subject)
!((subject.kind_of?(Symbol) || subject.kind_of?(String)) && has_instance_conditions?(action, subject))
end
# Accepts an array of actions and returns an array of actions which match.
# This should be called before "matches?" and other checking methods since they
# rely on the actions to be expanded.
def expand_aliases(type, items)
items.map do |item|
aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item
end.flatten
end
# Given an action, it will try to find all of the actions which are aliased to it.
# This does the opposite kind of lookup as expand_aliases.
def aliases_for(type, action)
results = [action]
aliases[type].each do |aliased_action, actions|
results += aliases_for(type, aliased_action) if actions.include? action
end
results
end
def rules
@rules ||= []
end
# Returns an array of Rule instances which match the action and subject
# This does not take into consideration any hash conditions or block statements
def relevant_rules(action, subject, attribute = nil)
specificity = 0
rules.reverse.each_with_object([]) do |rule, relevant_rules|
rule.expanded_actions = expand_aliases(:actions, rule.actions)
rule.expanded_subjects = expand_aliases(:subjects, rule.subjects)
if rule.relevant?(action, subject, attribute) && rule.specificity >= specificity
specificity = rule.specificity if rule.base_behavior
relevant_rules << rule
end
end
end
def includes_action?(actions, action)
actions.include?(:manage) || actions.include?(action)
def relevant_rules_for_match(action, subject, attribute)
relevant_rules(action, subject, attribute).each do |rule|
if rule.only_raw_sql?
raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
end
def includes_noun?(nouns, noun)
nouns.include?(:all) || nouns.include?(noun) || nouns.any? { |c| c.kind_of?(Class) && noun.kind_of?(c) }
def relevant_rules_for_query(action, subject)
relevant_rules(action, subject, nil).each do |rule|
if rule.only_block?
raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
end
def default_aliases
{
:subjects => {},
:actions => {
:read => [:index, :show],
:create => [:new],
:update => [:edit],
:destroy => [:delete],
}
}
end
end
end

View File

@@ -11,15 +11,19 @@ module CanCan
# load_and_authorize_resource
# end
#
def load_and_authorize_resource(options = {})
before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_and_authorize_resource }
def load_and_authorize_resource(*args)
cancan_resource_class.add_before_filter(self, {:load => true, :authorize => true}, *args)
end
# Sets up a before filter which loads the appropriate model resource into an instance variable.
# Sets up a before filter which loads the model resource into an instance variable.
# For example, given an ArticlesController it will load the current article into the @article
# instance variable. It does this by either calling Article.find(params[:id]) or
# Article.new(params[:article]) depending upon the action. It does nothing for the "index"
# action.
# Article.new(params[:article]) depending upon the action. The index action will
# automatically set @articles to Article.accessible_by(current_ability).
#
# If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
# the initial attributes based on these conditions. This way these actions will satisfy
# the ability restrictions.
#
# Call this method directly on the controller class.
#
@@ -41,6 +45,20 @@ module CanCan
# end
# end
#
# If a name is provided which does not match the controller it assumes it is a parent resource. Child
# resources can then be loaded through it.
#
# class BooksController < ApplicationController
# load_resource :author
# load_resource :book, :through => :author
# end
#
# Here the author resource will be loaded before each action using params[:author_id]. The book resource
# will then be loaded through the @author instance variable.
#
# That first argument is optional and will default to the singular name of the controller.
# A hash of options (see below) can also be passed to this method to further customize it.
#
# See load_and_authorize_resource to automatically authorize the resource too.
#
# Options:
@@ -50,22 +68,37 @@ module CanCan
# [:+except+]
# Does not apply before filter to given actions.
#
# [:+nested+]
# Specify which resource this is nested under.
# [:+through+]
# Load this resource through another one. This should match the name of the parent instance variable or method.
#
# load_resource :nested => :author
# [:+through_association+]
# The name of the association to fetch the child records through the parent resource. This is normally not needed
# because it defaults to the pluralized resource name.
#
# Deep nesting can be defined in an array.
# [:+shallow+]
# Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
#
# load_resource :nested => [:publisher, :author]
# [:+singleton+]
# Pass +true+ if this is a singleton resource through a +has_one+ association.
#
# [:+parent+]
# 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.
#
# [:+class+]
# The class to use for the model.
# The class to use for the model (string or constant).
#
# [:+instance_name+]
# The name of the instance variable to load the resource into.
#
# [:+find_by+]
# Find using a different attribute other than id. For example.
#
# load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
#
# [:+collection+]
# Specify which actions are resource collection actions in addition to :+index+. This
# is usually not necessary because it will try to guess depending on if an :+id+
# is present in +params+.
# is usually not necessary because it will try to guess depending on if the id param is present.
#
# load_resource :collection => [:sort, :list]
#
@@ -76,16 +109,20 @@ module CanCan
#
# load_resource :new => :build
#
def load_resource(options = {})
before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_resource }
# [:+prepend+]
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
#
def load_resource(*args)
raise ImplementationRemoved, "The load_resource method has been removed, use load_and_authorize_resource instead."
cancan_resource_class.add_before_filter(self, {:load => true}, *args)
end
# Sets up a before filter which authorizes the current resource using the instance variable.
# Sets up a before filter which authorizes the resource using the instance variable.
# For example, if you have an ArticlesController it will check the @article instance variable
# and ensure the user can perform the current action on it. Under the hood it is doing
# something like the following.
#
# unauthorized! if cannot?(params[:action].to_sym, @article || Article)
# authorize!(params[:action].to_sym, @article || Article)
#
# Call this method directly on the controller class.
#
@@ -93,6 +130,19 @@ module CanCan
# authorize_resource
# end
#
# If you pass in the name of a resource which does not match the controller it will assume
# it is a parent resource.
#
# class BooksController < ApplicationController
# authorize_resource :author
# authorize_resource :book
# end
#
# Here it will authorize :+show+, @+author+ on every action before authorizing the book.
#
# That first argument is optional and will default to the singular name of the controller.
# A hash of options (see below) can also be passed to this method to further customize it.
#
# See load_and_authorize_resource to automatically load the resource too.
#
# Options:
@@ -102,57 +152,204 @@ module CanCan
# [:+except+]
# Does not apply before filter to given actions.
#
# [:+class+]
# The class to use for the model.
# [:+singleton+]
# Pass +true+ if this is a singleton resource through a +has_one+ association.
#
def authorize_resource(options = {})
before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).authorize_resource }
# [:+parent+]
# 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.
#
# [:+class+]
# The class to use for the model (string or constant). This passed in when the instance variable is not set.
# Pass +false+ if there is no associated class for this resource and it will use a symbol of the resource name.
#
# [:+instance_name+]
# The name of the instance variable for this resource.
#
# [:+through+]
# 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)
raise ImplementationRemoved, "The authorize_resource method has been removed, use load_and_authorize_resource instead."
cancan_resource_class.add_before_filter(self, {:authorize => true}, *args)
end
# Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
# useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions
# to skip the effects on. It will apply to all actions by default.
#
# class ProjectsController < SomeOtherController
# skip_load_and_authorize_resource :only => :index
# end
#
# You can also pass the resource name as the first argument to skip that resource.
def skip_load_and_authorize_resource(*args)
skip_load_resource(*args)
skip_authorize_resource(*args)
end
# 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
# skip the effects on. It will apply to all actions by default.
#
# class ProjectsController < ApplicationController
# load_and_authorize_resource
# skip_load_resource :only => :index
# end
#
# You can also pass the resource name as the first argument to skip that resource.
def skip_load_resource(*args)
raise ImplementationRemoved, "The skip_load_resource method has been removed, use skip_load_and_authorize_resource instead."
options = args.extract_options!
name = args.first
cancan_skipper[:load][name] = options
end
# 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
# skip the effects on. It will apply to all actions by default.
#
# class ProjectsController < ApplicationController
# load_and_authorize_resource
# skip_authorize_resource :only => :index
# end
#
# You can also pass the resource name as the first argument to skip that resource.
def skip_authorize_resource(*args)
raise ImplementationRemoved, "The skip_authorize_resource method has been removed, use skip_load_and_authorize_resource instead."
options = args.extract_options!
name = args.first
cancan_skipper[:authorize][name] = options
end
# Add this to a controller to automatically perform authorization on every action.
#
# class ApplicationController < ActionController::Base
# enable_authorization
# end
#
# Internally it does this in a before_filter for every action.
#
# authorize! params[:action], params[:controller]
#
# If you need to "skip" authorization in a given controller, it is best to enable :+access+ to it in the +Ability+.
#
# Options:
# [:+only+]
# Only applies to given actions.
#
# [:+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
rescue_from(CanCan::Unauthorized, &block) if block
end
def cancan_resource_class
if ancestors.map(&:to_s).include? "InheritedResources::Actions"
InheritedResource
else
ControllerResource
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
raise ImplementationRemoved, "The skip_authorization_check method has been removed, instead authorize access to controller in Ability to 'skip'."
end
end
def self.included(base)
base.extend ClassMethods
base.helper_method :can?, :cannot?
base.helper_method :can?, :cannot?, :current_ability
end
# Raises the CanCan::AccessDenied exception. This is often used in a
# controller action to mark a request as unauthorized.
# Raises a CanCan::Unauthorized exception if the current_ability cannot
# perform the given action. This is usually called in a controller action or
# before filter to perform the authorization.
#
# def show
# @article = Article.find(params[:id])
# unauthorized! if cannot? :read, @article
# authorize! :read, @article
# end
#
# You can rescue from the exception in the controller to specify
# the user experience.
# A :message option can be passed to specify a different message.
#
# authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
#
# You can also use I18n to customize the message. Action aliases defined in Ability work here.
#
# en:
# unauthorized:
# manage:
# all: "Not authorized to %{action} %{subject}."
# user: "Not allowed to manage other user accounts."
# update:
# project: "Not allowed to update this project."
#
# You can rescue from the exception in the controller to customize how unauthorized
# access is displayed to the user.
#
# class ApplicationController < ActionController::Base
# rescue_from CanCan::AccessDenied, :with => :access_denied
#
# protected
#
# def access_denied
# flash[:error] = "Sorry, you are not allowed to access that page."
# redirect_to root_url
# rescue_from CanCan::Unauthorized do |exception|
# redirect_to root_url, :alert => exception.message
# end
# end
#
# See the load_and_authorize_resource method to automatically add
# the "unauthorized!" behavior to a RESTful controller's actions.
def unauthorized!
raise AccessDenied, "You are unable to access this page."
# See the CanCan::Unauthorized exception for more details on working with the exception.
#
# See the load_and_authorize_resource method to automatically add the authorize! behavior
# to the default RESTful actions.
def authorize!(*args)
@_authorized = true
current_ability.authorize!(*args)
end
# Creates and returns the current user's ability. You generally do not invoke
# this method directly, instead you can override this method to change its
# behavior if the Ability class or current_user method are different.
# Creates and returns the current user's ability and caches it. If you
# want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior.
#
# def current_ability
# UserAbility.new(current_account) # instead of Ability.new(current_user)
# # instead of Ability.new(current_user)
# @current_ability ||= UserAbility.new(current_account)
# end
#
# Notice it is important to cache the ability object so it is not
# recreated every time.
def current_ability
::Ability.new(current_user)
@current_ability ||= ::Ability.new(current_user)
end
# Use in the controller or view to check the user's permission for a given action
@@ -166,9 +363,16 @@ module CanCan
# <%= link_to "New Project", new_project_path %>
# <% end %>
#
# If it's a nested resource, you can pass the parent instance in a hash. This way it will
# check conditions which reach through that association.
#
# <% if can? :create, @category => Project %>
# <%= link_to "New Project", new_project_path %>
# <% end %>
#
# This simply calls "can?" on the current_ability. See Ability#can?.
def can?(*args)
(@current_ability ||= current_ability).can?(*args)
current_ability.can?(*args)
end
# Convenience method which works the same as "can?" but returns the opposite value.
@@ -176,12 +380,12 @@ module CanCan
# cannot? :destroy, @project
#
def cannot?(*args)
(@current_ability ||= current_ability).cannot?(*args)
current_ability.cannot?(*args)
end
end
end
if defined? ActionController
if defined? ActionController::Base
ActionController::Base.class_eval do
include CanCan::ControllerAdditions
end

View File

@@ -1,40 +1,266 @@
module CanCan
# Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
# This class is used internally, so you do not need to call methods directly on it.
class ControllerResource # :nodoc:
def initialize(controller, name, parent = nil, options = {})
def self.add_before_filter(controller_class, behavior, *args)
options = args.extract_options!.merge(behavior)
resource_name = args.first
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
controller_class.send(before_filter_method, options.slice(:only, :except, :if, :unless)) do |controller|
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).process
end
end
def initialize(controller, *args)
@controller = controller
@name = name
@parent = parent
@options = options
@params = controller.params
@options = args.extract_options!
@name = args.first
end
def model_class
@options[:class] || @name.to_s.camelize.constantize
def process
if @options[:load]
if load_instance?
self.resource_instance ||= load_resource_instance
elsif load_collection?
self.collection_instance ||= load_collection
current_ability.fully_authorized! @params[:action], @params[:controller]
end
def find(id)
self.model_instance ||= base.find(id)
end
def build(attributes)
if base.kind_of? Class
self.model_instance ||= base.new(attributes)
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
self.model_instance ||= base.build(attributes)
@controller.authorize!(authorization_action, resource_instance)
end
end
end
end
def model_instance
@controller.instance_variable_get("@#{@name}")
def parent?
@options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
end
def model_instance=(instance)
@controller.instance_variable_set("@#{@name}", instance)
# def skip?(behavior) # This could probably use some refactoring
# options = @controller.class.cancan_skipper[behavior][@name]
# if options.nil?
# false
# elsif options == {}
# true
# elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
# true
# elsif [options[:only]].flatten.include?(@params[:action].to_sym)
# true
# end
# end
protected
def load_resource_instance
if !parent? && new_actions.include?(@params[:action].to_sym)
build_resource
elsif id_param || @options[:singleton]
find_and_update_resource
end
end
private
def load_instance?
parent? || member_action?
end
def base
@parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class
def load_collection?
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, subject_name)
end
def load_collection
resource_base.accessible_by(current_ability, authorization_action)
end
def build_resource
resource = resource_base.new(resource_params || {})
assign_attributes(resource)
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
end
def initial_attributes
current_ability.attributes_for(@params[:action].to_sym, subject_name).delete_if do |key, value|
resource_params && resource_params.include?(key)
end
end
def find_and_update_resource
resource = find_resource
if resource_params
@controller.authorize!(authorization_action, resource) if @options[:authorize]
resource.attributes = resource_params
end
resource
end
def find_resource
if @options[:singleton] && parent_resource.respond_to?(name)
parent_resource.send(name)
else
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
else
adapter.find(resource_base, id_param)
end
end
end
def adapter
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
end
def authorization_action
parent? ? :show : @params[:action].to_sym
end
def id_param
if @options[:id_param]
@params[@options[:id_param]]
else
@params[parent? ? :"#{name}_id" : :id]
end
end
def member_action?
new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
end
# Returns the class used for this resource. This can be overriden by the :class option.
# If +false+ is passed in it will use the resource name as a symbol in which case it should
# only be used for authorization, not loading since there's no class to load through.
def resource_class
case @options[:class]
when false then name.to_sym
when nil then namespaced_name.to_s.camelize.constantize
when String then @options[:class].constantize
else @options[:class]
end
end
def subject_name
resource_class.to_s.underscore.pluralize.to_sym
end
def subject_name_with_parent
parent_resource ? {parent_resource => subject_name} : subject_name
end
def resource_instance=(instance)
@controller.instance_variable_set("@#{instance_name}", instance)
end
def resource_instance
if load_instance?
if @controller.instance_variable_defined? "@#{instance_name}"
@controller.instance_variable_get("@#{instance_name}")
elsif @controller.respond_to?(instance_name, true)
@controller.send(instance_name)
end
end
end
def collection_instance=(instance)
@controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
end
def collection_instance
@controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
end
# The object that methods (such as "find", "new" or "build") are called on.
# If the :through option is passed it will go through an association on that instance.
# If the :shallow option is passed it will use the resource_class if there's no parent
# If the :singleton option is passed it won't use the association because it needs to be handled later.
def resource_base
if @options[:through]
if parent_resource
@options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
elsif @options[:shallow]
resource_class
else
raise Unauthorized.new(nil, authorization_action, @params[:controller].to_sym) # maybe this should be a record not found error instead?
end
else
resource_class
end
end
def parent_name
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
end
# The object to load this resource through.
def parent_resource
parent_name && fetch_parent(parent_name)
end
def fetch_parent(name)
if @controller.instance_variable_defined? "@#{name}"
@controller.instance_variable_get("@#{name}")
elsif @controller.respond_to?(name, true)
@controller.send(name)
end
end
def current_ability
@controller.send(:current_ability)
end
def name
@name || name_from_controller
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
@params[:controller].sub("Controller", "").underscore.split('/').last.singularize
end
def instance_name
@options[:instance_name] || name
end
def collection_actions
[:index] + [@options[:collection]].flatten
end
def new_actions
[:new, :create] + [@options[:new]].flatten
end
end
end

53
lib/cancan/exceptions.rb Normal file
View File

@@ -0,0 +1,53 @@
module CanCan
# A general CanCan exception
class Error < StandardError; end
# Raised when behavior is not implemented, usually used in an abstract class.
class NotImplemented < Error; end
# Raised when removed code is called, an alternative solution is provided in message.
class ImplementationRemoved < Error; end
# Raised when using check_authorization without calling authorized!
class AuthorizationNotPerformed < Error; end
# Raised when enable_authorization is used and not fully authorized by the end of the action
class InsufficientAuthorizationCheck < Error; end
# This error is raised when a user isn't allowed to access a given controller action.
# This usually happens within a call to ControllerAdditions#authorize! but can be
# raised manually.
#
# raise CanCan::Unauthorized.new("Not authorized!", :read, Article)
#
# The passed message, action, and subject are optional and can later be retrieved when
# rescuing from the exception.
#
# exception.message # => "Not authorized!"
# exception.action # => :read
# exception.subject # => Article
#
# If the message is not specified (or is nil) it will default to "You are not authorized
# to access this page." This default can be overridden by setting default_message.
#
# exception.default_message = "Default error message"
# exception.message # => "Default error message"
#
# See ControllerAdditions#authorize! for more information on rescuing from this exception
# and customizing the message using I18n.
class Unauthorized < Error
attr_reader :action, :subject
attr_writer :default_message
def initialize(message = nil, action = nil, subject = nil)
@message = message
@action = action
@subject = subject
@default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
end
def to_s
@message || @default_message
end
end
end

View File

@@ -0,0 +1,20 @@
module CanCan
# For use with Inherited Resources
class InheritedResource < ControllerResource # :nodoc:
def load_resource_instance
if parent?
@controller.send :association_chain
@controller.instance_variable_get("@#{instance_name}")
elsif new_actions.include? @params[:action].to_sym
resource = @controller.send :build_resource
assign_attributes(resource)
else
@controller.send :resource
end
end
def resource_base
@controller.send :end_of_association_chain
end
end
end

14
lib/cancan/matchers.rb Normal file
View File

@@ -0,0 +1,14 @@
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # for RSpec 1 compatability
Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
match do |ability|
ability.can?(*args)
end
failure_message_for_should do |ability|
"expected to be able to #{args.map(&:inspect).join(" ")}"
end
failure_message_for_should_not do |ability|
"expected not to be able to #{args.map(&:inspect).join(" ")}"
end
end

View File

@@ -0,0 +1,56 @@
module CanCan
module ModelAdapters
class AbstractAdapter
def self.inherited(subclass)
@subclasses ||= []
@subclasses << subclass
end
def self.adapter_class(model_class)
@subclasses.detect { |subclass| subclass.for_class?(model_class) } || DefaultAdapter
end
# Used to determine if the given adapter should be used for the passed in class.
def self.for_class?(member_class)
false # override in subclass
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.
# If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
def self.override_conditions_hash_matching?(subject, conditions)
false
end
# Override if override_conditions_hash_matching? returns true
def self.matches_conditions_hash?(subject, conditions)
raise NotImplemented, "This model adapter does not support matching on a conditions hash."
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)
@model_class = model_class
@rules = rules
end
def database_records
# This should be overridden in a subclass to return records which match @rules
raise NotImplemented, "This model adapter does not support fetching records from the database."
end
end
end
end

View File

@@ -0,0 +1,172 @@
module CanCan
module ModelAdapters
class ActiveRecordAdapter < AbstractAdapter
def self.for_class?(model_class)
model_class <= ActiveRecord::Base
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
# method directly, but instead go through ModelAdditions#accessible_by.
#
# If there is only one "can" definition, a hash of conditions will be returned matching the one defined.
#
# can :manage, User, :id => 1
# query(:manage, User).conditions # => { :id => 1 }
#
# If there are multiple "can" definitions, a SQL string will be returned to handle complex cases.
#
# can :manage, User, :id => 1
# can :manage, User, :manager_id => 1
# cannot :manage, User, :self_managed => true
# query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
#
def conditions
if @rules.size == 1 && @rules.first.base_behavior
# Return the conditions directly if there's just one definition
tableized_conditions(@rules.first.conditions).dup
else
@rules.reverse.inject(false_sql) do |sql, rule|
merge_conditions(sql, tableized_conditions(rule.conditions).dup, rule.base_behavior)
end
end
end
def tableized_conditions(conditions, model_class = @model_class)
return conditions unless conditions.kind_of? Hash
conditions.inject({}) do |result_hash, (name, value)|
if value.kind_of? Hash
association_class = model_class.reflect_on_association(name).class_name.constantize
name = model_class.reflect_on_association(name).table_name.to_sym
value = tableized_conditions(value, association_class)
end
result_hash[name] = value
result_hash
end
end
# Returns the associations used in conditions for the :joins option of a search.
# See ModelAdditions#accessible_by
def joins
joins_hash = {}
@rules.each do |rule|
merge_joins(joins_hash, rule.associations_hash)
end
clean_joins(joins_hash) unless joins_hash.empty?
end
def database_records
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)
else
@model_class.where(*(@rules.map(&:conditions))).joins(joins)
end
else
@model_class.scoped(:conditions => conditions, :joins => joins)
end
end
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)
if conditions_hash.blank?
behavior ? true_sql : false_sql
else
conditions = sanitize_sql(conditions_hash)
case sql
when true_sql
behavior ? true_sql : "not (#{conditions})"
when false_sql
behavior ? conditions : false_sql
else
behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
end
end
end
def false_sql
sanitize_sql(['?=?', true, false])
end
def true_sql
sanitize_sql(['?=?', true, true])
end
def sanitize_sql(conditions)
@model_class.send(:sanitize_sql, conditions)
end
# Takes two hashes and does a deep merge.
def merge_joins(base, add)
add.each do |name, nested|
if base[name].is_a?(Hash) && !nested.empty?
merge_joins(base[name], nested)
else
base[name] = nested
end
end
end
# Removes empty hashes and moves everything into arrays.
def clean_joins(joins_hash)
joins = []
joins_hash.each do |name, nested|
joins << (nested.empty? ? name : {name => clean_joins(nested)})
end
joins
end
end
end
end
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.class_eval do
include CanCan::ModelAdditions
end
end

View File

@@ -0,0 +1,34 @@
module CanCan
module ModelAdapters
class DataMapperAdapter < AbstractAdapter
def self.for_class?(model_class)
model_class <= DataMapper::Resource
end
def self.find(model_class, id)
model_class.get(id)
end
def self.override_conditions_hash_matching?(subject, conditions)
conditions.any? { |k,v| !k.kind_of?(Symbol) }
end
def self.matches_conditions_hash?(subject, conditions)
collection = DataMapper::Collection.new(subject.query, [ subject ])
!!collection.first(conditions)
end
def database_records
scope = @model_class.all(:conditions => ["0 = 1"])
cans, cannots = @rules.partition { |r| r.base_behavior }
return scope if cans.empty?
# 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
end
end # class DataMapper
end # module ModelAdapters
end # module CanCan
DataMapper::Model.append_extensions(CanCan::ModelAdditions::ClassMethods)

View File

@@ -0,0 +1,7 @@
module CanCan
module ModelAdapters
class DefaultAdapter < AbstractAdapter
# This adapter is used when no matching adapter is found
end
end
end

View File

@@ -0,0 +1,54 @@
module CanCan
module ModelAdapters
class MongoidAdapter < AbstractAdapter
def self.for_class?(model_class)
model_class <= Mongoid::Document
end
def self.override_conditions_hash_matching?(subject, conditions)
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
def self.matches_conditions_hash?(subject, conditions)
# To avoid hitting the db, retrieve the raw Mongo selector from
# the Mongoid Criteria and use Mongoid::Matchers#matches?
subject.matches?( subject.class.where(conditions).selector )
end
def database_records
if @rules.size == 0
@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
# we only need to process can rules if
# there are no rules with empty 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
records
end
end
end
end
end
end
end
# simplest way to add `accessible_by` to all Mongoid Documents
module Mongoid::Document::ClassMethods
include CanCan::ModelAdditions::ClassMethods
end

View File

@@ -0,0 +1,29 @@
module CanCan
# This module adds the accessible_by class method to a model. It is included in the model adapters.
module ModelAdditions
extend ActiveSupport::Concern
module ClassMethods
# Returns a scope which fetches only the records that the passed ability
# can perform a given action on. The action defaults to :index. This
# is usually called from a controller and passed the +current_ability+.
#
# @articles = Article.accessible_by(current_ability)
#
# Here only the articles which the user is able to read will be returned.
# If the user does not have permission to read any articles then an empty
# result is returned. Since this is a scope it can be combined with any
# other scopes or pagination.
#
# An alternative action can optionally be passed as a second argument.
#
# @articles = Article.accessible_by(current_ability, :update)
#
# Here only the articles which the user can update are returned.
def accessible_by(ability, action = :index)
ability.model_adapter(self, action).database_records
end
end
end
end

View File

@@ -1,62 +0,0 @@
module CanCan
class ResourceAuthorization # :nodoc:
attr_reader :params
def initialize(controller, params, options = {})
@controller = controller
@params = params
@options = options
end
def load_and_authorize_resource
load_resource
authorize_resource
end
def load_resource
unless collection_actions.include? params[:action].to_sym
if new_actions.include? params[:action].to_sym
resource.build(params[model_name.to_sym])
elsif params[:id]
resource.find(params[:id])
end
end
end
def authorize_resource
@controller.unauthorized! if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
end
private
def resource
@resource ||= ControllerResource.new(@controller, model_name, parent_resource, @options)
end
def parent_resource
parent = nil
[@options[:nested]].flatten.compact.each do |name|
id = @params["#{name}_id".to_sym]
if id
parent = ControllerResource.new(@controller, name, parent)
parent.find(id)
else
parent = nil
end
end
parent
end
def model_name
params[:controller].split('/').last.singularize
end
def collection_actions
[:index] + [@options[:collection]].flatten
end
def new_actions
[:new, :create] + [@options[:new]].flatten
end
end
end

178
lib/cancan/rule.rb Normal file
View File

@@ -0,0 +1,178 @@
module CanCan
# This class is used internally and should only be called through Ability.
# it holds the information about a "can" call made on Ability and provides
# helpful methods to determine permission checking and conditions hash generation.
class Rule # :nodoc:
attr_reader :base_behavior, :subjects, :actions, :conditions
attr_writer :expanded_actions, :expanded_subjects
# The first argument when initializing is the base_behavior which is a true/false
# value. True for "can" and false for "cannot". The next two arguments are the action
# and subject respectively (such as :read, @project). The third argument is a hash
# of conditions and the last one is the block passed to the "can" call.
def initialize(base_behavior, action = nil, subject = nil, *extra_args, &block)
@match_all = action.nil? && subject.nil?
@base_behavior = base_behavior
@actions = [action].flatten
@subjects = [subject].flatten
@attributes = [extra_args.shift].flatten if extra_args.first.kind_of?(Symbol) || extra_args.first.kind_of?(Array) && extra_args.first.first.kind_of?(Symbol)
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if extra_args.first.kind_of?(Hash) && !block.nil?
@conditions = extra_args.first || {}
@block = block
end
# Matches the subject, action, and given attribute. Conditions are not checked here.
def relevant?(action, subject, attribute)
subject = subject.values.first if subject.class == Hash
@match_all || (matches_action?(action) && matches_subject?(subject) && matches_attribute?(attribute))
end
# Matches the block or conditions hash
def matches_conditions?(action, subject, attribute)
if @match_all
call_block_with_all(action, subject, attribute)
elsif @block && subject_object?(subject)
@block.arity == 1 ? @block.call(subject) : @block.call(subject, attribute)
elsif @conditions.kind_of?(Hash) && subject.class == Hash
nested_subject_matches_conditions?(subject)
elsif @conditions.kind_of?(Hash) && subject_object?(subject)
matches_conditions_hash?(subject)
else
# Don't stop at "cannot" definitions when there are conditions.
@conditions.empty? ? true : @base_behavior
end
end
def only_block?
!conditions? && !@block.nil?
end
def only_raw_sql?
@block.nil? && conditions? && !@conditions.kind_of?(Hash)
end
def attributes?
@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
def associations_hash(conditions = @conditions)
hash = {}
conditions.map do |name, value|
hash[name] = associations_hash(value) if value.kind_of? Hash
end if conditions.kind_of? Hash
hash
end
def attributes_from_conditions
attributes = {}
@conditions.each do |key, value|
attributes[key] = value unless [Array, Range, Hash].include? value.class
end if @conditions.kind_of? Hash
attributes
end
def specificity
specificity = 1
specificity += 1 if attributes? || conditions?
specificity += 2 unless base_behavior
specificity
end
private
def subject_object?(subject)
# klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
# klass == Class || klass == Module
!subject.kind_of?(Symbol) && !subject.kind_of?(String)
end
def matches_action?(action)
@expanded_actions.include?(:access) || @expanded_actions.include?(action.to_sym)
end
def matches_subject?(subject)
subject = subject_name(subject) if subject_object? subject
@expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
end
def matches_attribute?(attribute)
# don't consider attributes in a cannot clause when not matching - this can probably be refactored
if !@base_behavior && @attributes && attribute.nil?
false
else
@attributes.nil? || attribute.nil? || @attributes.include?(attribute.to_sym)
end
end
# TODO deperecate this
def matches_subject_class?(subject)
@expanded_subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
end
# Checks if the given subject matches the given conditions hash.
# This behavior can be overriden by a model adapter by defining two class methods:
# override_matching_for_conditions?(subject, conditions) and
# matches_conditions_hash?(subject, conditions)
def matches_conditions_hash?(subject, conditions = @conditions)
if conditions.empty?
true
else
if model_adapter(subject).override_conditions_hash_matching? subject, conditions
model_adapter(subject).matches_conditions_hash? subject, conditions
else
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)
if value.kind_of?(Hash)
if attribute.kind_of? Array
attribute.any? { |element| matches_conditions_hash? element, value }
else
attribute && matches_conditions_hash?(attribute, value)
end
elsif value.kind_of?(Enumerable)
value.include? attribute
else
attribute == value
end
end
end
end
end
end
def nested_subject_matches_conditions?(subject_hash)
parent, child = subject_hash.first
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
end
def call_block_with_all(action, subject, attribute)
if subject_object? subject
@block.call(action, subject_name(subject), subject, attribute)
else
@block.call(action, subject, nil, attribute)
end
end
def subject_name(subject)
subject.class.to_s.underscore.pluralize.to_sym
end
def model_adapter(subject)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject)
end
end
end

View File

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

View File

@@ -0,0 +1,16 @@
module Cancan
module Generators
class AbilityGenerator < Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__)
def generate_ability
copy_file "ability.rb", "app/models/ability.rb"
if File.exist?(File.join(destination_root, "spec"))
copy_file "ability_spec.rb", "spec/models/ability_spec.rb"
else
copy_file "ability_test.rb", "test/unit/ability_test.rb"
end
end
end
end
end

View File

@@ -0,0 +1,24 @@
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in (current) user. 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 :access and :all
# to represent any action and controller respectively. Passing an array to either of
# these will grant permission on each item in the array.
#
# 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

28
spec/README.rdoc Normal file
View File

@@ -0,0 +1,28 @@
= CanCan Specs
== Running the specs
To run the specs first run the +bundle+ command to install the necessary gems and the +rake+ command to run the specs.
bundle
rake
The specs currently require Ruby 1.8.7. Ruby 1.9.2 support will be coming soon.
== Model Adapters
CanCan offers separate specs for different model adapters (such as Mongoid and Data Mapper). By default it will use Active Record but you can change this by setting the +MODEL_ADAPTER+ environment variable before running. You can run the +bundle+ command with this as well to ensure you have the installed gems.
MODEL_ADAPTER=data_mapper bundle
MODEL_ADAPTER=data_mapper rake
The different model adapters you can specify are:
* active_record (default)
* data_mapper
* mongoid
You can also run the +spec_all+ rake task to run specs for each adapter.
rake spec_all

View File

@@ -1,126 +1,541 @@
require File.dirname(__FILE__) + '/../spec_helper'
require "spec_helper"
describe CanCan::Ability do
before(:each) do
@ability_class = Class.new
@ability_class.send(:include, CanCan::Ability)
@ability = @ability_class.new
@ability = Object.new
@ability.extend(CanCan::Ability)
end
it "should be able to :read anything" do
@ability.can :read, :all
@ability.can?(:read, String).should be_true
@ability.can?(:read, 123).should be_true
# Basic Action & Subject
it "allows access to only what is defined" do
@ability.can?(:paint, :fences).should be_false
@ability.can :paint, :fences
@ability.can?(:paint, :fences).should be_true
@ability.can?(:wax, :fences).should be_false
@ability.can?(:paint, :cars).should be_false
end
it "should not have permission to do something it doesn't know about" do
@ability.can?(:foodfight, String).should be_false
it "allows access to everything when :access, :all is used" do
@ability.can?(:paint, :fences).should be_false
@ability.can :access, :all
@ability.can?(:paint, :fences).should be_true
@ability.can?(:wax, :fences).should be_true
@ability.can?(:paint, :cars).should be_true
end
it "should return what block returns on a can call" do
@ability.can :read, :all
@ability.can :read, Symbol do |sym|
sym
end
@ability.can?(:read, Symbol).should be_nil
@ability.can?(:read, :some_symbol).should == :some_symbol
it "allows access to multiple actions and subjects" do
@ability.can [:paint, :sand], [:fences, :decks]
@ability.can?(:paint, :fences).should be_true
@ability.can?(:sand, :fences).should be_true
@ability.can?(:paint, :decks).should be_true
@ability.can?(:sand, :decks).should be_true
@ability.can?(:wax, :fences).should be_false
@ability.can?(:paint, :cars).should be_false
end
it "should pass class with object if :all objects are accepted" do
@ability.can :preview, :all do |object_class, object|
[object_class, object]
end
@ability.can?(:preview, 123).should == [Fixnum, 123]
it "allows strings instead of symbols in ability check" do
@ability.can :paint, :fences
@ability.can?("paint", "fences").should be_true
end
it "should pass class with no object if :all objects are accepted and class is passed directly" do
@ability.can :preview, :all do |object_class, object|
[object_class, object]
end
@ability.can?(:preview, Hash).should == [Hash, nil]
# Aliases
it "has default index, show, new, update, delete aliases" do
@ability.can :read, :projects
@ability.can?(:index, :projects).should be_true
@ability.can?(:show, :projects).should be_true
@ability.can :create, :projects
@ability.can?(:new, :projects).should be_true
@ability.can :update, :projects
@ability.can?(:edit, :projects).should be_true
@ability.can :destroy, :projects
@ability.can?(:delete, :projects).should be_true
end
it "should pass action and object for global manage actions" do
@ability.can :manage, Array do |action, object|
[action, object]
end
@ability.can?(:stuff, [1, 2]).should == [:stuff, [1, 2]]
@ability.can?(:stuff, Array).should == [:stuff, nil]
end
it "should alias update or destroy actions to modify action" do
it "follows deep action aliases" do
@ability.alias_action :update, :destroy, :to => :modify
@ability.can :modify, :all do |object_class, object|
:modify_called
end
@ability.can?(:update, 123).should == :modify_called
@ability.can?(:destroy, 123).should == :modify_called
@ability.can :modify, :projects
@ability.can?(:update, :projects).should be_true
@ability.can?(:destroy, :projects).should be_true
@ability.can?(:edit, :projects).should be_true
end
it "should return block result for action, object_class, and object for any action" do
@ability.can :manage, :all do |action, object_class, object|
[action, object_class, object]
end
@ability.can?(:foo, 123).should == [:foo, Fixnum, 123]
@ability.can?(:bar, Fixnum).should == [:bar, Fixnum, nil]
it "adds up action aliases" do
@ability.alias_action :update, :to => :modify
@ability.alias_action :destroy, :to => :modify
@ability.can :modify, :projects
@ability.can?(:update, :projects).should be_true
@ability.can?(:destroy, :projects).should be_true
end
it "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
it "follows deep subject aliases" do
@ability.alias_subject :mammals, :to => :animals
@ability.alias_subject :cats, :to => :mammals
@ability.can :pet, :animals
@ability.can?(:pet, :mammals).should be_true
end
it "should automatically alias new and edit into create and update respectively" do
@ability.can(:create, :all) { :create_called }
@ability.can(:update, :all) { :update_called }
@ability.can?(:new, 123).should == :create_called
@ability.can?(:edit, 123).should == :update_called
it "clears current and default aliases" do
@ability.alias_action :update, :destroy, :to => :modify
@ability.clear_aliases
@ability.can :modify, :projects
@ability.can?(:update, :projects).should be_false
@ability.can :read, :projects
@ability.can?(:show, :projects).should be_false
end
it "should 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
# Hash Conditions
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, Array]
@ability.can?(:update, "foo").should be_true
@ability.can?(:update, []).should be_true
@ability.can?(:update, 123).should be_false
end
it "should support custom objects in the can definition" 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 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
it "maps object to pluralized subject name" do
@ability.can :read, :ranges
@ability.can?(:read, :ranges).should be_true
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 123).should be_false
end
it "should support block on 'cannot' method" do
it "checks conditions hash on instances only" do
@ability.can :read, :ranges, :begin => 1
@ability.can?(:read, :ranges).should be_true
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 2..4).should be_false
end
it "checks conditions on both rules and matches either one" do
@ability.can :read, :ranges, :begin => 1
@ability.can :read, :ranges, :begin => 2
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 2..4).should be_true
@ability.can?(:read, 3..5).should be_false
end
it "checks an array of options in conditions hash" do
@ability.can :read, :ranges, :begin => [1, 3, 5]
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 2..4).should be_false
@ability.can?(:read, 3..5).should be_true
end
it "checks a range of options in conditions hash" do
@ability.can :read, :ranges, :begin => 1..3
@ability.can?(:read, 1..10).should be_true
@ability.can?(:read, 3..30).should be_true
@ability.can?(:read, 4..40).should be_false
end
it "checks nested conditions hash" do
@ability.can :read, :ranges, :begin => { :to_i => 5 }
@ability.can?(:read, 5..7).should be_true
@ability.can?(:read, 6..8).should be_false
end
it "matches any element passed in to nesting if it's an array (for has_many associations)" do
@ability.can :read, :ranges, :to_a => { :to_i => 3 }
@ability.can?(:read, 1..5).should be_true
@ability.can?(:read, 4..6).should be_false
end
it "takes presedence over rule defined without a condition" do
@ability.can :read, :ranges
@ability.can :read, :ranges, :begin => 1
@ability.can?(:read, 1..5).should be_true
@ability.can?(:read, 4..6).should be_false
end
# Block Conditions
it "executes block passing object only when instance is used" do
@ability.can :read, :ranges do |range|
range.begin == 5
end
@ability.can?(:read, :ranges).should be_true
@ability.can?(:read, 5..7).should be_true
@ability.can?(:read, 6..8).should be_false
end
it "returns true when other object is returned in block" do
@ability.can :read, :ranges do |range|
"foo"
end
@ability.can?(:read, 5..7).should be_true
end
it "passes to previous rule when block returns false" do
@ability.can :read, :fixnums do |i|
i < 5
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.cannot :read, Integer do |int|
int > 5
@ability.cannot :read, :ranges
@ability.can?(:read, :books).should be_true
@ability.can?(:read, 1..3).should be_false
@ability.can?(:read, :ranges).should be_false
end
@ability.can?(:read, "foo").should be_true
@ability.can?(:read, 3).should be_true
@ability.can?(:read, 123).should be_false
it "passes to previous rule if cannot check returns false" do
@ability.can :read, :all
@ability.cannot :read, :ranges, :begin => 3
@ability.cannot :read, :ranges do |range|
range.begin == 5
end
@ability.can?(:read, :books).should be_true
@ability.can?(:read, 2..4).should be_true
@ability.can?(:read, 3..7).should be_false
@ability.can?(:read, 5..9).should be_false
end
it "rejects permission only to a given attribute" do
@ability.can :update, :books
@ability.cannot :update, :books, :author
@ability.can?(:update, :books).should be_true
@ability.can?(:update, :books, :author).should be_false
end
# Hash Association
it "checks permission through association when hash is passed as subject" do
@ability.can :read, :books, :range => {:begin => 3}
@ability.can?(:read, (1..4) => :books).should be_false
@ability.can?(:read, (3..5) => :books).should be_true
@ability.can?(:read, 123 => :books).should be_true
end
it "checks permissions on association hash with multiple rules" do
@ability.can :read, :books, :range => {:begin => 3}
@ability.can :read, :books, :range => {:end => 6}
@ability.can?(:read, (1..4) => :books).should be_false
@ability.can?(:read, (3..5) => :books).should be_true
@ability.can?(:read, (1..6) => :books).should be_true
@ability.can?(:read, 123 => :books).should be_true
end
it "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
@ability.authorize! :read, :books, :message => "Access denied!"
rescue CanCan::Unauthorized => e
e.message.should == "Access denied!"
e.action.should == :read
e.subject.should == :books
else
fail "Expected CanCan::Unauthorized exception to be raised"
end
end
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do
@ability.can :read, :foo
lambda {
@ability.authorize!(:read, :foo).should == :foo
}.should_not raise_error
end
it "knows when block is used in conditions" do
@ability.can :read, :foo
@ability.should_not have_block(:read, :foo)
@ability.can :read, :foo do |foo|
false
end
@ability.should have_block(:read, :foo)
end
it "knows when raw sql is used in conditions" do
@ability.can :read, :foo
@ability.should_not have_raw_sql(:read, :foo)
@ability.can :read, :foo, 'false'
@ability.should have_raw_sql(:read, :foo)
end
it "raises access denied exception with default message if not specified" do
begin
@ability.authorize! :read, :books
rescue CanCan::Unauthorized => e
e.default_message = "Access denied!"
e.message.should == "Access denied!"
else
fail "Expected CanCan::Unauthorized exception to be raised"
end
end
it "does not raise access denied exception if ability is authorized to perform an action and return subject" do
@ability.can :read, :books
lambda {
@ability.authorize!(:read, :books).should == :books
}.should_not raise_error
end
# Determining Kind of Conditions
it "knows when a block is used for conditions" do
@ability.can :read, :books
@ability.should_not have_block(:read, :books)
@ability.can :read, :books do |foo|
false
end
@ability.should have_block(:read, :books)
end
it "knows when raw sql is used for conditions" do
@ability.can :read, :books
@ability.should_not have_raw_sql(:read, :books)
@ability.can :read, :books, 'false'
@ability.should have_raw_sql(:read, :books)
end
it "determines model adapter class by asking AbstractAdapter" do
model_class = Object.new
adapter_class = Object.new
CanCan::ModelAdapters::AbstractAdapter.stub(:adapter_class).with(model_class) { adapter_class }
adapter_class.stub(:new).with(model_class, []) { :adapter_instance }
@ability.model_adapter(model_class, :read).should == :adapter_instance
end
# Unauthorized I18n Message
describe "unauthorized message" do
after(:each) do
I18n.backend = nil
end
it "uses action/subject in i18n" do
I18n.backend.store_translations :en, :unauthorized => {:update => {:ranges => "update ranges"}}
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
@ability.unauthorized_message(:update, 2..4).should == "update ranges"
@ability.unauthorized_message(:update, :missing).should be_nil
end
it "uses symbol as subject directly" do
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
@ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
end
it "falls back to 'access' and 'all'" do
I18n.backend.store_translations :en, :unauthorized => {
:access => {:all => "access all", :ranges => "access ranges"},
:update => {:all => "update all", :ranges => "update ranges"}
}
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
@ability.unauthorized_message(:update, :hashes).should == "update all"
@ability.unauthorized_message(:create, :ranges).should == "access ranges"
@ability.unauthorized_message(:create, :hashes).should == "access all"
end
it "follows aliases" do
I18n.backend.store_translations :en, :unauthorized => {:modify => {:ranges => "modify ranges"}}
@ability.alias_action :update, :to => :modify
@ability.alias_subject :areas, :to => :ranges
@ability.unauthorized_message(:update, :areas).should == "modify ranges"
@ability.unauthorized_message(:edit, :ranges).should == "modify ranges"
end
it "has variables for action and subject" do
I18n.backend.store_translations :en, :unauthorized => {:access => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
@ability.unauthorized_message(:edit, 1..3).should == "edit ranges"
# @ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
end
end
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

View File

@@ -1,47 +1,118 @@
require File.dirname(__FILE__) + '/../spec_helper'
require "spec_helper"
describe CanCan::ControllerAdditions do
before(:each) do
@params = HashWithIndifferentAccess.new
@controller_class = Class.new
@controller = @controller_class.new
stub(@controller).params { {} }
mock(@controller_class).helper_method(:can?, :cannot?)
@controller.stub(:params) { @params }
@controller.stub(:current_user) { :current_user }
@controller_class.should_receive(:helper_method).with(:can?, :cannot?, :current_ability)
@controller_class.send(:include, CanCan::ControllerAdditions)
end
it "should read from the cache with request uri as key and render that text" do
lambda {
@controller.unauthorized!
}.should raise_error(CanCan::AccessDenied)
it "raises ImplementationRemoved when attempting to call load/authorize/skip/check calls on a controller" do
lambda { @controller_class.load_resource }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.authorize_resource }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.skip_load_resource }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.skip_authorize_resource }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.check_authorization }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.skip_authorization_check }.should raise_error(CanCan::ImplementationRemoved)
lambda { @controller_class.cancan_skipper }.should raise_error(CanCan::ImplementationRemoved)
end
it "should have a current_ability method which generates an ability for the current user" do
stub(@controller).current_user { :current_user }
@controller.current_ability.should be_kind_of(Ability)
it "authorize! should pass args to current ability" do
@controller.current_ability.should_receive(:authorize!).with(:foo, :bar)
@controller.authorize!(:foo, :bar)
end
it "should provide a can? and cannot? methods which go through the current ability" do
stub(@controller).current_user { :current_user }
it "provides a can? and cannot? methods which go through the current ability" do
@controller.current_ability.should be_kind_of(Ability)
@controller.can?(:foo, :bar).should be_false
@controller.cannot?(:foo, :bar).should be_true
end
it "load_and_authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource
mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
it "load_and_authorize_resource adds a before filter which passes call to ControllerResource" do
controller_resource = double("controller_resource")
controller_resource.should_receive(:process)
CanCan::ControllerResource.stub(:new).with(@controller, nil, :load => true, :authorize => true, :foo => :bar) { controller_resource }
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
@controller_class.load_and_authorize_resource :foo => :bar
end
it "authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource
mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
@controller_class.authorize_resource :foo => :bar, :except => :show
it "load_and_authorize_resource passes first argument as the resource name" do
controller_resource = double("controller_resource")
controller_resource.should_receive(:process)
CanCan::ControllerResource.stub(:new).with(@controller, :project, :load => true, :authorize => true, :foo => :bar) { controller_resource }
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
@controller_class.load_and_authorize_resource :project, :foo => :bar
end
it "load_resource should setup a before filter which passes call to ResourceAuthorization" do
stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_resource
mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) }
@controller_class.load_resource :foo => :bar, :only => [:show, :index]
it "load_and_authorize_resource passes :only, :except, :if, :unless options to before filter" do
controller_resource = double("controller_resource")
controller_resource.should_receive(:process)
CanCan::ControllerResource.stub(:new).with(@controller, nil, :load => true, :authorize => true) { controller_resource }
@controller_class.should_receive(:before_filter).with(:only => 1, :except => 2, :if => 3, :unless => 4).and_yield(@controller)
@controller_class.load_and_authorize_resource :only => 1, :except => 2, :if => 3, :unless => 4
end
it "load_and_authorize_resource with :prepend prepends the before filter" do
@controller_class.should_receive(:prepend_before_filter).with({})
@controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
end
it "cancan_resource_class should be ControllerResource by default" do
@controller.class.cancan_resource_class.should == CanCan::ControllerResource
end
it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do
@controller.class.stub(:ancestors) { ["InheritedResources::Actions"] }
@controller.class.cancan_resource_class.should == CanCan::InheritedResource
end
it "enable_authorization should call authorize! with controller and action name" do
@params.merge!(:controller => "projects", :action => "create")
@controller.should_receive(:authorize!).with("create", "projects")
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar).and_yield(@controller)
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar)
@controller_class.enable_authorization(:only => :foo, :except => :bar)
end
it "enable_authorization should raise InsufficientAuthorizationCheck when not fully authoried" do
@params.merge!(:controller => "projects", :action => "create")
@controller_class.stub(:before_filter).with(:only => :foo, :except => :bar)
@controller_class.stub(:after_filter).with(:only => :foo, :except => :bar).and_yield(@controller)
lambda {
@controller_class.enable_authorization(:only => :foo, :except => :bar)
}.should raise_error(CanCan::InsufficientAuthorizationCheck)
end
it "enable_authorization should not call authorize! when :if is false" do
@authorize_called = false
@controller.stub(:authorize?) { false }
@controller.stub(:authorize!) { @authorize_called = true }
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
@controller_class.should_receive(:after_filter).with({}).and_yield(@controller)
@controller_class.enable_authorization(:if => :authorize?)
@authorize_called.should be_false
end
it "enable_authorization should not call authorize! when :unless is true" do
@authorize_called = false
@controller.stub(:engine_controller?) { true }
@controller.stub(:authorize!) { @authorize_called = true }
@controller_class.should_receive(:before_filter).with({}).and_yield(@controller)
@controller_class.should_receive(:after_filter).with({}).and_yield(@controller)
@controller_class.enable_authorization(:unless => :engine_controller?)
@authorize_called.should be_false
end
it "enable_authorization should pass block to rescue_from CanCan::Unauthorized call" do
@block_called = false
@controller_class.should_receive(:before_filter).with({})
@controller_class.should_receive(:after_filter).with({})
@controller_class.should_receive(:rescue_from).with(CanCan::Unauthorized).and_yield(:exception)
@controller_class.enable_authorization { |e| @block_called = (e == :exception) }
@block_called.should be_true
end
end

View File

@@ -1,49 +1,551 @@
require File.dirname(__FILE__) + '/../spec_helper'
require "spec_helper"
describe CanCan::ControllerResource do
before(:each) do
@controller = Object.new
Project.delete_all
Category.delete_all
@params = HashWithIndifferentAccess.new(:controller => "projects")
@controller_class = Class.new
@controller = @controller_class.new
@ability = Ability.new(nil)
@controller.stub(:params) { @params }
@controller.stub(:current_ability) { @ability }
@controller.stub(:authorize!) { |*args| @ability.authorize!(*args) }
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
end
it "should determine model class by constantizing give name" do
CanCan::ControllerResource.new(@controller, :ability).model_class.should == Ability
it "loads the resource into an instance variable if params[:id] is specified" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == project
end
it "should fetch model through model class and assign it to the instance" do
stub(Ability).find(123) { :some_ability }
CanCan::ControllerResource.new(@controller, :ability).find(123)
@controller.instance_variable_get(:@ability).should == :some_ability
it "does not load resource into an instance variable if already set" do
@params.merge!(:action => "show", :id => 123)
@controller.instance_variable_set(:@project, :some_project)
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "should fetch model through parent and assign it to the instance" do
parent = Object.new
stub(parent).model_instance.stub!.abilities.stub!.find(123) { :some_ability }
CanCan::ControllerResource.new(@controller, :ability, parent).find(123)
@controller.instance_variable_get(:@ability).should == :some_ability
it "loads resource for namespaced controller" do
project = Project.create!
@params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == project
end
it "should build model through model class and assign it to the instance" do
stub(Ability).new(123) { :some_ability }
CanCan::ControllerResource.new(@controller, :ability).build(123)
@controller.instance_variable_get(:@ability).should == :some_ability
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do
module SomeEngine
class Project < ::Project; end
end
project = SomeEngine::Project.create!
@params.merge!(:controller => "SomeEngine::ProjectsController", :action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == project
end
it "should build model through parent and assign it to the instance" do
parent = Object.new
stub(parent).model_instance.stub!.abilities.stub!.build(123) { :some_ability }
CanCan::ControllerResource.new(@controller, :ability, parent).build(123)
@controller.instance_variable_get(:@ability).should == :some_ability
# Rails includes namespace in params, see issue #349
it "creates through the namespaced params" do
module SomeEngine
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"
end
it "should not load resource if instance variable is already provided" do
@controller.instance_variable_set(:@ability, :some_ability)
CanCan::ControllerResource.new(@controller, :ability).find(123)
@controller.instance_variable_get(:@ability).should == :some_ability
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 "should use the model class option if provided" do
stub(Person).find(123) { :some_resource }
CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person).find(123)
@controller.instance_variable_get(:@ability).should == :some_resource
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")
@ability.can(:create, :projects, :name => "from conditions")
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).name.should == "from conditions"
end
it "overrides initial attributes with params" do
@params.merge!(:action => "new", :project => {:name => "from params"})
@ability.can(:create, :projects, :name => "from conditions")
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).name.should == "from params"
end
it "builds a collection when on index action when class responds to accessible_by and mark ability as fully authorized" do
Project.stub(:accessible_by).with(@ability, :index) { :found_projects }
@params[:action] = "index"
CanCan::ControllerResource.new(@controller, :project, :load => true).process
@controller.instance_variable_get(:@project).should be_nil
@controller.instance_variable_get(:@projects).should == :found_projects
@ability.should be_fully_authorized(:index, :projects)
end
it "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(:@projects).should == :found_projects
end
it "builds a resource when on custom new action even when params[:id] exists" do
@params.merge!(:action => "build", :id => 123)
Project.stub(:new) { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :new => :build).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "does not try to load resource for other action if params[:id] is undefined" do
@params[:action] = "list"
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should be_nil
end
it "is a parent resource when name is provided which doesn't match controller" do
resource = CanCan::ControllerResource.new(@controller, :category)
resource.should be_parent
end
it "does not be a parent resource when name is provided which matches controller" do
resource = CanCan::ControllerResource.new(@controller, :project)
resource.should_not be_parent
end
it "is parent if specified in options" do
resource = CanCan::ControllerResource.new(@controller, :project, {:parent => true})
resource.should be_parent
end
it "does not be parent if specified in options" do
resource = CanCan::ControllerResource.new(@controller, :category, {:parent => false})
resource.should_not be_parent
end
it "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!
@params.merge!(:action => "index", :project_id => project.id)
CanCan::ControllerResource.new(@controller, :project, :load => true, :parent => true).process
@controller.instance_variable_get(:@project).should == project
end
it "loads resource through the association of another parent resource using instance variable" do
@params.merge!(:action => "show", :id => 123)
category = double("category", :projects => double("projects"))
category.projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "loads resource through the custom association name" do
@params.merge!(:action => "show", :id => 123)
category = double("category", :custom_projects => double("custom_projects"))
category.custom_projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :through_association => :custom_projects).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "loads resource through the association of another parent resource using method" do
@params.merge!(:action => "show", :id => 123)
category = double("category", :projects => double("projects"))
@controller.stub(:category) { category }
category.projects.stub(:find).with(123) { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :through => :category).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "does not load through parent resource if instance isn't loaded when shallow" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :shallow => true).process
@controller.instance_variable_get(:@project).should == project
end
it "raises Unauthorized when attempting to load resource through nil" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :load => true, :through => :category)
lambda {
resource.process
}.should raise_error(CanCan::Unauthorized) { |exception|
exception.action.should == :show
exception.subject.should == :projects
}
@controller.instance_variable_get(:@project).should be_nil
end
it "named resources should be loaded independently of the controller name" do
category = Category.create!
@params.merge!(:action => "new", :category_id => category.id)
CanCan::ControllerResource.new(@controller, :category, :load => true).process
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category).process
@controller.instance_variable_get(:@category).should eq(category)
project = @controller.instance_variable_get(:@project)
project.category.should eq(category)
end
it "parent resources shouldn't be altered" do
category = Category.create!
@params.merge!(:action => "create", :category_id => category.id, :project => { :name => 'foo' })
CanCan::ControllerResource.new(@controller, :category, :load => true).process
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category).process
project = @controller.instance_variable_get(:@project)
project.should be_new_record
project.name.should eq('foo')
end
it "does not overrides an attribute if it is based on parent resource" do
user = double('user')
user.should_receive(:category_id).and_return nil
@ability = Ability.new user
@ability.can :new, :projects, :category_id => user.category_id
category = Category.create!
@controller.instance_variable_set(:@category, category)
@params.merge! :action => 'new', :category_id => category.id
CanCan::ControllerResource.new(@controller, :category, :load => true).process
CanCan::ControllerResource.new(@controller, :project, :load => true, :through => :category, :singleton => true).process
project = @controller.instance_variable_get(:@project)
project.category_id.should_not be_nil
project.category.should eq(category)
end
it "authorizes nested resource through parent association on index action" do
pending
@params.merge!(:action => "index")
category = Object.new
@controller.instance_variable_set(:@category, category)
@controller.stub(:authorize!).with(:index, category => :projects) { raise CanCan::Unauthorized }
resource = CanCan::ControllerResource.new(@controller, :authorize => true, :through => :category)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end
it "loads through first matching if multiple are given" do
@params.merge!(:action => "show", :id => 123)
category = double("category", :projects => double("projects"))
category.projects.stub(:find).with(123) { :some_project }
@controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => [:category, :user]).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "finds record through has_one association with :singleton option without id param" do
@params.merge!(:action => "show", :id => nil)
category = Object.new
@controller.instance_variable_set(:@category, category)
category.stub(:project) { :some_project }
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process
@controller.instance_variable_get(:@project).should == :some_project
end
it "does not build record through has_one association with :singleton option because it can cause it to delete it in the database" do
@params.merge!(:action => "create", :project => {:name => "foobar"})
category = Category.new
@controller.instance_variable_set(:@category, category)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true).process
@controller.instance_variable_get(:@project).name.should == "foobar"
@controller.instance_variable_get(:@project).category.should == category
end
it "finds record through has_one association with :singleton and :shallow options" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process
@controller.instance_variable_get(:@project).should == project
end
it "builds record through has_one association with :singleton and :shallow options" do
@params.merge!(:action => "create", :project => {:name => "foobar"})
CanCan::ControllerResource.new(@controller, :load => true, :through => :category, :singleton => true, :shallow => true).process
@controller.instance_variable_get(:@project).name.should == "foobar"
end
it "only authorizes :show action on parent resource" do
project = Project.create!
@params.merge!(:action => "new", :project_id => project.id)
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized }
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true, :parent => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end
it "authorizes update action before setting attributes" do
@ability.can :update, :projects, :name => "bar"
project = Project.create!(:name => "foo")
@params.merge!(:action => "update", :id => project.id, :project => {:name => "bar"})
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end
it "authorizes update action after setting attributes" do
@ability.can :update, :projects, :name => "foo"
project = Project.create!(:name => "foo")
@params.merge!(:action => "update", :id => project.id, :project => {:name => "bar"})
resource = CanCan::ControllerResource.new(@controller, :project, :load => true, :authorize => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end
it "loads the model using a custom class" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :class => Project).process
@controller.instance_variable_get(:@project).should == project
end
it "loads the model using a custom namespaced class" do
module SomeEngine
class Project < ::Project; end
end
project = SomeEngine::Project.create!
@params.merge!(:action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true, :class => SomeEngine::Project).process
@controller.instance_variable_get(:@project).should == project
end
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!
@params.merge!(:action => "show", :id => project.id)
@controller.stub(:authorize!).with(:show, project) { raise CanCan::Unauthorized }
resource = CanCan::ControllerResource.new(@controller, :load => true, :authorize => true, :instance_name => :custom_project)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
@controller.instance_variable_get(:@custom_project).should == project
end
it "loads resource using custom ID param" do
project = Project.create!
@params.merge!(:action => "show", :the_project => project.id)
resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project, :load => true)
resource.process
@controller.instance_variable_get(:@project).should == project
end
it "loads resource using custom find_by attribute" do
project = Project.create!(:name => "foo")
@params.merge!(:action => "show", :id => "foo")
CanCan::ControllerResource.new(@controller, :load => true, :find_by => :name).process
@controller.instance_variable_get(:@project).should == project
end
it "authorizes each new attribute in the create action" do
@params.merge!(:action => "create", :project => {:name => "foo"})
@controller.instance_variable_set(:@project, :some_project)
@ability.should_receive(:authorize!).with(:create, :some_project, :name)
CanCan::ControllerResource.new(@controller, :authorize => true).process
end
it "allows full find method to be passed into find_by option" do
project = Project.create!(:name => "foo")
@params.merge!(:action => "show", :id => "foo")
CanCan::ControllerResource.new(@controller, :find_by => :find_by_name, :load => true).process
@controller.instance_variable_get(:@project).should == project
end
it "authorizes each new attribute in the update action" do
@params.merge!(:action => "update", :id => 123, :project => {:name => "foo"})
@controller.instance_variable_set(:@project, :some_project)
@ability.should_receive(:authorize!).with(:update, :some_project, :name)
CanCan::ControllerResource.new(@controller, :authorize => true).process
end
it "fetches member through method when instance variable is not provided" do
@controller.stub(:project) { :some_project }
@params.merge!(:action => "show", :id => 123)
@controller.stub(:authorize!).with(:show, :some_project) { raise CanCan::Unauthorized }
resource = CanCan::ControllerResource.new(@controller, :authorize => true)
lambda { resource.process }.should raise_error(CanCan::Unauthorized)
end
it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do
module Namespaced
class Project < ::Project; end
end
project = Namespaced::Project.create!
@params.merge!(:controller => "Namespaced::ProjectsController", :action => "show", :id => project.id)
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == project
end
# Rails includes namespace in params, see issue #349
it "creates through namespaced params" do
module Namespaced
class Project < ::Project; end
end
@params.merge!(:controller => "Namespaced::ProjectsController", :action => "create", :namespaced_project => {:name => "foobar"})
CanCan::ControllerResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).name.should == "foobar"
end
it "should properly authorize resource for namespaced controller" do
@ability.can(:index, "admin/dashboard")
@params.merge!(:controller => "admin/dashboard", :action => "index")
@controller.authorize!(:index, "admin/dashboard")
resource = CanCan::ControllerResource.new(@controller, :authorize => true).process
lambda { resource.process }.should_not raise_error(CanCan::Unauthorized)
end
# it "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

View File

@@ -0,0 +1,58 @@
require "spec_helper"
describe CanCan::Unauthorized do
describe "with action and subject" do
before(:each) do
@exception = CanCan::Unauthorized.new(nil, :some_action, :some_subject)
end
it "has action and subject accessors" do
@exception.action.should == :some_action
@exception.subject.should == :some_subject
end
it "has a changable default message" do
@exception.message.should == "You are not authorized to access this page."
@exception.default_message = "Unauthorized!"
@exception.message.should == "Unauthorized!"
end
end
describe "with only a message" do
before(:each) do
@exception = CanCan::Unauthorized.new("Access denied!")
end
it "has nil action and subject" do
@exception.action.should be_nil
@exception.subject.should be_nil
end
it "has passed message" do
@exception.message.should == "Access denied!"
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

View File

@@ -0,0 +1,58 @@
require "spec_helper"
describe CanCan::InheritedResource do
before(:each) do
@params = HashWithIndifferentAccess.new(:controller => "projects")
@controller_class = Class.new
@controller = @controller_class.new
@ability = Ability.new(nil)
@controller.stub(:params) { @params }
@controller.stub(:current_ability) { @ability }
# @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
end
it "show should load resource through @controller.resource" do
@params.merge!(:action => "show", :id => 123)
@controller.stub(:resource) { :project_resource }
CanCan::InheritedResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == :project_resource
end
it "new should load through @controller.build_resource" do
@params[:action] = "new"
@controller.stub(:build_resource) { :project_resource }
CanCan::InheritedResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@project).should == :project_resource
end
it "index should load through @controller.association_chain when parent" do
@params[:action] = "index"
@controller.stub(:association_chain) { @controller.instance_variable_set(:@project, :project_resource) }
CanCan::InheritedResource.new(@controller, :load => true, :parent => true).process
@controller.instance_variable_get(:@project).should == :project_resource
end
it "index should load through @controller.end_of_association_chain" do
@params[:action] = "index"
Project.stub(:accessible_by).with(@ability, :index) { :projects }
@controller.stub(:end_of_association_chain) { Project }
CanCan::InheritedResource.new(@controller, :load => true).process
@controller.instance_variable_get(:@projects).should == :projects
end
it "should build a new resource with attributes from current ability" do
@params[:action] = "new"
@ability.can(:create, :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

View File

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

View File

@@ -0,0 +1,278 @@
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
require "spec_helper"
class Category
has_many :articles
end
class Article < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.integer :category_id
t.string :name
t.boolean :published
t.boolean :secret
t.integer :priority
end
belongs_to :category
has_many :comments
end
class Comment < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.integer :article_id
t.boolean :spam
end
belongs_to :article
end
describe CanCan::ModelAdapters::ActiveRecordAdapter do
before(:each) do
Article.delete_all
Comment.delete_all
@ability = Object.new
@ability.extend(CanCan::Ability)
@article_table = Article.table_name
@comment_table = Comment.table_name
end
it "is for only active record classes" do
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
end
it "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.accessible_by(@ability).should be_empty
end
it "fetches all articles when one can read all" do
@ability.can :read, :articles
article = Article.create!
Article.accessible_by(@ability).should == [article]
end
it "fetches only the articles that are published" do
@ability.can :read, :articles, :published => true
article1 = Article.create!(:published => true)
article2 = Article.create!(:published => false)
Article.accessible_by(@ability).should == [article1]
end
it "fetches any articles which are published or secret" do
@ability.can :read, :articles, :published => true
@ability.can :read, :articles, :secret => true
article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true)
article4 = Article.create!(:published => false, :secret => false)
Article.accessible_by(@ability).should == [article1, article2, article3]
end
it "fetches only the articles that are published and not secret" do
@ability.can :read, :articles, :published => true
@ability.cannot :read, :articles, :secret => true
article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true)
article4 = Article.create!(:published => false, :secret => false)
Article.accessible_by(@ability).should == [article1]
end
it "only reads comments for articles which are published" do
@ability.can :read, :comments, :article => { :published => true }
comment1 = Comment.create!(:article => Article.create!(:published => true))
comment2 = Comment.create!(:article => Article.create!(:published => false))
Comment.accessible_by(@ability).should == [comment1]
end
it "only reads comments for visible categories through articles" do
pending "does ActiveRecord no longer support a deep nested hash of conditions?"
@ability.can :read, :comments, :article => { :category => { :visible => true } }
comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true)))
comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false)))
Comment.accessible_by(@ability).should == [comment1]
end
it "allows conditions in SQL and merge with hash conditions" do
@ability.can :read, :articles, :published => true
@ability.can :read, :articles, ["secret=?", true]
article1 = Article.create!(:published => true, :secret => false)
article2 = Article.create!(:published => true, :secret => true)
article3 = Article.create!(:published => false, :secret => true)
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]
end
it "fetches only associated records when using with a scope for conditions" do
@ability.can :read, :articles, Article.where(:secret => true)
category1 = Category.create!(:visible => false)
category2 = Category.create!(:visible => true)
article1 = Article.create!(:secret => true, :category => category1)
article2 = Article.create!(:secret => true, :category => category2)
# 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
end
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
end
it "does not allow to check ability on object against SQL conditions without block" do
@ability.can :read, :articles, ["secret=?", true]
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
end
it "has false conditions if no abilities match" do
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end
it "returns false conditions for cannot clause" do
@ability.cannot :read, :articles
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end
it "returns SQL for single `can` definition in front of default `cannot` condition" do
@ability.cannot :read, :articles
@ability.can :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
end
it "returns true condition for single `can` definition in front of default `can` condition" do
@ability.can :read, :articles
@ability.can :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should eq(:secret => true, :published => false)
end
it "returns `false condition` for single `cannot` definition in front of default `cannot` condition" do
@ability.cannot :read, :articles
@ability.cannot :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
end
it "returns `not (sql)` for single `cannot` definition in front of default `can` condition" do
@ability.can :read, :articles
@ability.cannot :read, :articles, :published => false, :secret => true
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
end
it "returns appropriate sql conditions in complex case" do
@ability.can :read, :articles
@ability.can :access, :articles, :id => 1
@ability.can :update, :articles, :published => true
@ability.cannot :update, :articles, :secret => true
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
@ability.model_adapter(Article, :access).conditions.should == {:id => 1}
@ability.model_adapter(Article, :read).conditions.should == {:id => 1} # used to be "t=t" but changed with new specificity rule (issue #321)
end
it "does not forget conditions when calling with SQL string" do
@ability.can :read, :articles, :published => true
@ability.can :read, :articles, ['secret=?', false]
adapter = @ability.model_adapter(Article, :read)
2.times do
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
end
end
it "has nil joins if no rules" do
@ability.model_adapter(Article, :read).joins.should be_nil
end
it "has nil joins if no nested hashes specified in conditions" do
@ability.can :read, :articles, :published => false
@ability.can :read, :articles, :secret => true
@ability.model_adapter(Article, :read).joins.should be_nil
end
it "merges separate joins into a single array" do
@ability.can :read, :articles, :project => { :blocked => false }
@ability.can :read, :articles, :company => { :admin => true }
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
end
it "merges same joins into a single array" do
@ability.can :read, :articles, :project => { :blocked => false }
@ability.can :read, :articles, :project => { :admin => true }
@ability.model_adapter(Article, :read).joins.should == [:project]
end
it "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

View File

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

View File

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

View File

@@ -0,0 +1,226 @@
if ENV["MODEL_ADAPTER"] == "mongoid"
require "spec_helper"
class MongoidCategory
include Mongoid::Document
references_many :mongoid_projects
end
class MongoidProject
include Mongoid::Document
referenced_in :mongoid_category
end
Mongoid.configure do |config|
config.master = Mongo::Connection.new('127.0.0.1', 27017).db("cancan_mongoid_spec")
end
describe CanCan::ModelAdapters::MongoidAdapter do
context "Mongoid defined" do
before(:each) do
@ability = Object.new
@ability.extend(CanCan::Ability)
end
after(:each) do
Mongoid.master.collections.select do |collection|
collection.name !~ /system/
end.each(&:drop)
end
it "is for only Mongoid classes" do
CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
end
it "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
@ability.can :read, :mongoid_projects, :id => model.id
@ability.should be_able_to(:read, model)
end
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 => 'Lord')
MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :read).entries.should == []
end
it "returns the correct records based on the defined ability" do
@ability.can :read, :mongoid_projects, :title => "Sir"
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end
it "returns the correct records when a mix of can and cannot rules in defined ability" do
pending "TODO figure out why this isn't working"
@ability.can :manage, :mongoid_projects, :title => 'Sir'
@ability.cannot :destroy, :mongoid_projects
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
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')
lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
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
it "handles :field.in" do
obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj]
obj2 = MongoidProject.create(:title => 'Lord')
@ability.can?(:read, obj2).should == false
end
describe "activates only when there are Criteria in the hash" do
it "Calls where on the model class when there are criteria" do
obj = MongoidProject.create(:title => 'Bird')
@conditions = {:title.nin => ["Fork", "Spoon"]}
@ability.can :read, :mongoid_projects, @conditions
@ability.should be_able_to(:read, obj)
end
it "Calls the base version if there are no mongoid criteria" do
obj = MongoidProject.new(:title => 'Bird')
@conditions = {:id => obj.id}
@ability.can :read, :mongoid_projects, @conditions
@ability.should be_able_to(:read, obj)
end
end
it "handles :field.nin" do
obj = MongoidProject.create(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.nin => ["Lord", "Madam"]
@ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj]
obj2 = MongoidProject.create(:title => 'Lord')
@ability.can?(:read, obj2).should == false
end
it "handles :field.size" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, :mongoid_projects, :titles.size => 2
@ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj]
obj2 = MongoidProject.create(:titles => ['Palatin', 'Margrave', 'Marquis'])
@ability.can?(:read, obj2).should == false
end
it "handles :field.exists" do
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
@ability.can :read, :mongoid_projects, :titles.exists => true
@ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj]
obj2 = MongoidProject.create
@ability.can?(:read, obj2).should == false
end
it "handles :field.gt" do
obj = MongoidProject.create(:age => 50)
@ability.can :read, :mongoid_projects, :age.gt => 45
@ability.can?(:read, obj).should == true
MongoidProject.accessible_by(@ability, :read).should == [obj]
obj2 = MongoidProject.create(:age => 40)
@ability.can?(:read, obj2).should == false
end
it "handles instance not saved to database" do
obj = MongoidProject.new(:title => 'Sir')
@ability.can :read, :mongoid_projects, :title.in => ["Sir", "Madam"]
@ability.can?(:read, obj).should == true
# accessible_by only returns saved records
MongoidProject.accessible_by(@ability, :read).entries.should == []
obj2 = MongoidProject.new(:title => 'Lord')
@ability.can?(:read, obj2).should == false
end
end
it "calls where with matching ability conditions" do
obj = MongoidProject.create(:foo => {:bar => 1})
@ability.can :read, :mongoid_projects, :foo => {:bar => 1}
MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
end
it "excludes from the result if set to cannot" do
obj = MongoidProject.create(:bar => 1)
obj2 = MongoidProject.create(:bar => 2)
@ability.can :read, :mongoid_projects
@ability.cannot :read, :mongoid_projects, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should == [obj]
end
it "combines the rules" do
obj = MongoidProject.create(:bar => 1)
obj2 = MongoidProject.create(:bar => 2)
obj3 = MongoidProject.create(:bar => 3)
@ability.can :read, :mongoid_projects, :bar => 1
@ability.can :read, :mongoid_projects, :bar => 2
MongoidProject.accessible_by(@ability, :read).entries.should =~ [obj, obj2]
end
it "does not allow to fetch records when ability with just block present" do
@ability.can :read, :mongoid_projects do
false
end
lambda {
MongoidProject.accessible_by(@ability)
}.should raise_error(CanCan::Error)
end
end
end
end

View File

@@ -1,115 +0,0 @@
require File.dirname(__FILE__) + '/../spec_helper'
describe CanCan::ResourceAuthorization do
before(:each) do
@controller = Object.new # simple stub for now
stub(@controller).unauthorized! { raise CanCan::AccessDenied }
end
it "should load the resource into an instance variable if params[:id] is specified" do
stub(Ability).find(123) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show", :id => 123)
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
it "should properly load resource for namespaced controller" do
stub(Ability).find(123) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "admin/abilities", :action => "show", :id => 123)
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
it "should build a new resource with hash if params[:id] is not specified" do
stub(Ability).new(:foo => "bar") { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "create", :ability => {:foo => "bar"})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
it "should build a new resource even if attribute hash isn't specified" do
stub(Ability).new(nil) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "new")
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
it "should not build a resource when on index action" do
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "index")
authorization.load_resource
@controller.instance_variable_get(:@ability).should be_nil
end
it "should perform authorization using controller action and loaded model" do
@controller.instance_variable_set(:@ability, :some_resource)
stub(@controller).cannot?(:show, :some_resource) { true }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
lambda {
authorization.authorize_resource
}.should raise_error(CanCan::AccessDenied)
end
it "should perform authorization using controller action and non loaded model" do
stub(@controller).cannot?(:show, Ability) { true }
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
lambda {
authorization.authorize_resource
}.should raise_error(CanCan::AccessDenied)
end
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
mock(authorization).load_resource
mock(authorization).authorize_resource
authorization.load_and_authorize_resource
end
it "should not build a resource when on custom collection action" do
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "sort"}, {:collection => [:sort, :list]})
authorization.load_resource
@controller.instance_variable_get(:@ability).should be_nil
end
it "should build a resource when on custom new action even when params[:id] exists" do
stub(Ability).new(nil) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "build", :id => 123}, {:new => :build})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
it "should not try to load resource for other action if params[:id] is undefined" do
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "list")
authorization.load_resource
@controller.instance_variable_get(:@ability).should be_nil
end
it "should load nested resource and fetch other resource through the association" do
stub(Person).find(456).stub!.abilities.stub!.find(123) { :some_ability }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123, :person_id => 456}, {:nested => :person})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_ability
end
it "should load nested resource and build resource through a deep association" do
stub(Person).find(456).stub!.behaviors.stub!.find(789).stub!.abilities.stub!.build(nil) { :some_ability }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456, :behavior_id => 789}, {:nested => [:person, :behavior]})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_ability
end
it "should not load nested resource and build through this if *_id param isn't specified" do
stub(Person).find(456) { :some_person }
stub(Ability).new(nil) { :some_ability }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456}, {:nested => [:person, :behavior]})
authorization.load_resource
@controller.instance_variable_get(:@person).should == :some_person
@controller.instance_variable_get(:@ability).should == :some_ability
end
it "should load the model using a custom class" do
stub(Person).find(123) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:class => Person})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
end

55
spec/cancan/rule_spec.rb Normal file
View File

@@ -0,0 +1,55 @@
require "spec_helper"
require "ostruct" # for OpenStruct below
# Most of Rule functionality is tested in Ability specs
describe CanCan::Rule do
before(:each) do
@conditions = {}
@rule = CanCan::Rule.new(true, :read, :integers, @conditions)
end
it "returns no association joins if none exist" do
@rule.associations_hash.should == {}
end
it "returns no association for joins if just attributes" do
@conditions[:foo] = :bar
@rule.associations_hash.should == {}
end
it "returns single association for joins" do
@conditions[:foo] = {:bar => 1}
@rule.associations_hash.should == {:foo => {}}
end
it "returns multiple associations for joins" do
@conditions[:foo] = {:bar => 1}
@conditions[:test] = {1 => 2}
@rule.associations_hash.should == {:foo => {}, :test => {}}
end
it "returns nested associations for joins" do
@conditions[:foo] = {:bar => {1 => 2}}
@rule.associations_hash.should == {:foo => {:bar => {}}}
end
it "returns no association joins if conditions is nil" do
rule = CanCan::Rule.new(true, :read, :integers)
rule.associations_hash.should == {}
end
it "has higher specificity for attributes/conditions" do
CanCan::Rule.new(true, :read, :integers).specificity.should eq(1)
CanCan::Rule.new(true, :read, :integers, :foo => :bar).specificity.should eq(2)
CanCan::Rule.new(true, :read, :integers, :foo).specificity.should eq(2)
CanCan::Rule.new(false, :read, :integers).specificity.should eq(3)
CanCan::Rule.new(false, :read, :integers, :foo => :bar).specificity.should eq(4)
CanCan::Rule.new(false, :read, :integers, :foo).specificity.should eq(4)
end
it "should not be mergeable if conditions are not simple hashes" do
meta_where = OpenStruct.new(:name => 'metawhere', :column => 'test')
@conditions[meta_where] = :bar
@rule.should be_unmergeable
end
end

13
spec/matchers.rb Normal file
View File

@@ -0,0 +1,13 @@
RSpec::Matchers.define :orderlessly_match do |original_string|
match do |given_string|
original_string.split('').sort == given_string.split('').sort
end
failure_message_for_should do |given_string|
"expected \"#{given_string}\" to have the same characters as \"#{original_string}\""
end
failure_message_for_should_not do |given_string|
"expected \"#{given_string}\" not to have the same characters as \"#{original_string}\""
end
end

View File

@@ -1,13 +1,27 @@
require 'rubygems'
require 'spec'
require 'active_support'
require 'active_record'
require 'action_controller'
require 'action_view'
require File.dirname(__FILE__) + '/../lib/cancan.rb'
require 'bundler/setup'
Spec::Runner.configure do |config|
config.mock_with :rr
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 'active_support/all'
require 'matchers'
require 'cancan'
require 'cancan/matchers'
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.filter_run :focus => true
config.run_all_when_everything_filtered = true
end
class Ability
@@ -17,6 +31,19 @@ class Ability
end
end
# this class helps out in testing nesting
class Person
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
class Category < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.boolean :visible
end
has_many :projects
end
class Project < ActiveRecord::Base
connection.create_table(table_name) do |t|
t.integer :category_id
t.string :name
end
belongs_to :category
end