cancan 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +21 -0
- data/Gemfile +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +20 -90
- data/Rakefile +8 -0
- data/lib/cancan/ability.rb +24 -26
- data/lib/cancan/controller_additions.rb +50 -0
- data/lib/cancan/controller_resource.rb +33 -15
- data/lib/cancan/exceptions.rb +3 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +40 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +119 -0
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +33 -0
- data/lib/cancan/model_adapters/default_adapter.rb +7 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +41 -0
- data/lib/cancan/{active_record_additions.rb → model_additions.rb} +5 -16
- data/lib/cancan/{can_definition.rb → rule.rb} +29 -25
- data/lib/cancan.rb +8 -3
- data/lib/generators/cancan/ability/USAGE +4 -0
- data/lib/generators/cancan/ability/ability_generator.rb +11 -0
- data/lib/generators/cancan/ability/templates/ability.rb +28 -0
- data/spec/README.rdoc +28 -0
- data/spec/cancan/ability_spec.rb +11 -3
- data/spec/cancan/controller_additions_spec.rb +30 -0
- data/spec/cancan/controller_resource_spec.rb +68 -2
- data/spec/cancan/inherited_resource_spec.rb +3 -1
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +185 -0
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +115 -0
- data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +168 -0
- data/spec/cancan/rule_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -24
- metadata +24 -16
- data/lib/cancan/query.rb +0 -97
- data/spec/cancan/active_record_additions_spec.rb +0 -75
- data/spec/cancan/can_definition_spec.rb +0 -57
- data/spec/cancan/query_spec.rb +0 -107
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module CanCan
|
|
2
|
+
module ModelAdapters
|
|
3
|
+
class DataMapperAdapter < AbstractAdapter
|
|
4
|
+
def self.for_class?(model_class)
|
|
5
|
+
model_class <= DataMapper::Resource
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.override_conditions_hash_matching?(subject, conditions)
|
|
9
|
+
conditions.any? { |k,v| !k.kind_of?(Symbol) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.matches_conditions_hash?(subject, conditions)
|
|
13
|
+
subject.class.all(:conditions => conditions).include?(subject) # TODO don't use a database query here for performance and other instances
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def database_records
|
|
17
|
+
scope = @model_class.all(:conditions => ["0=1"])
|
|
18
|
+
conditions.each do |condition|
|
|
19
|
+
scope += @model_class.all(:conditions => condition)
|
|
20
|
+
end
|
|
21
|
+
scope
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def conditions
|
|
25
|
+
@rules.map(&:conditions)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
DataMapper::Model.class_eval do
|
|
32
|
+
include CanCan::ModelAdditions::ClassMethods
|
|
33
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module CanCan
|
|
2
|
+
module ModelAdapters
|
|
3
|
+
class MongoidAdapter < AbstractAdapter
|
|
4
|
+
def self.for_class?(model_class)
|
|
5
|
+
model_class <= Mongoid::Document
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.override_conditions_hash_matching?(subject, conditions)
|
|
9
|
+
conditions.any? { |k,v| !k.kind_of?(Symbol) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.matches_conditions_hash?(subject, conditions)
|
|
13
|
+
# To avoid hitting the db, retrieve the raw Mongo selector from
|
|
14
|
+
# the Mongoid Criteria and use Mongoid::Matchers#matches?
|
|
15
|
+
subject.matches?( subject.class.where(conditions).selector )
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def database_records
|
|
19
|
+
@model_class.where(conditions)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def conditions
|
|
23
|
+
if @rules.size == 0
|
|
24
|
+
false_query
|
|
25
|
+
else
|
|
26
|
+
@rules.first.conditions
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def false_query
|
|
31
|
+
# this query is sure to return no results
|
|
32
|
+
{:_id => {'$exists' => false, '$type' => 7}} # type 7 is an ObjectID (default for _id)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# simplest way to add `accessible_by` to all Mongoid Documents
|
|
39
|
+
module Mongoid::Document::ClassMethods
|
|
40
|
+
include CanCan::ModelAdditions::ClassMethods
|
|
41
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module CanCan
|
|
2
|
-
|
|
3
|
-
module
|
|
2
|
+
|
|
3
|
+
# This module adds the accessible_by class method to a model. It is included in the model adapters.
|
|
4
|
+
module ModelAdditions
|
|
4
5
|
module ClassMethods
|
|
5
6
|
# Returns a scope which fetches only the records that the passed ability
|
|
6
7
|
# can perform a given action on. The action defaults to :read. This
|
|
@@ -17,15 +18,9 @@ module CanCan
|
|
|
17
18
|
#
|
|
18
19
|
# @articles = Article.accessible_by(current_ability, :update)
|
|
19
20
|
#
|
|
20
|
-
# Here only the articles which the user can update are returned.
|
|
21
|
-
# internally uses Ability#conditions method, see that for more information.
|
|
21
|
+
# Here only the articles which the user can update are returned.
|
|
22
22
|
def accessible_by(ability, action = :read)
|
|
23
|
-
|
|
24
|
-
if respond_to?(:where) && respond_to?(:joins)
|
|
25
|
-
where(query.conditions).joins(query.joins)
|
|
26
|
-
else
|
|
27
|
-
scoped(:conditions => query.conditions, :joins => query.joins)
|
|
28
|
-
end
|
|
23
|
+
ability.model_adapter(self, action).database_records
|
|
29
24
|
end
|
|
30
25
|
end
|
|
31
26
|
|
|
@@ -34,9 +29,3 @@ module CanCan
|
|
|
34
29
|
end
|
|
35
30
|
end
|
|
36
31
|
end
|
|
37
|
-
|
|
38
|
-
if defined? ActiveRecord
|
|
39
|
-
ActiveRecord::Base.class_eval do
|
|
40
|
-
include CanCan::ActiveRecordAdditions
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -2,8 +2,8 @@ module CanCan
|
|
|
2
2
|
# This class is used internally and should only be called through Ability.
|
|
3
3
|
# it holds the information about a "can" call made on Ability and provides
|
|
4
4
|
# helpful methods to determine permission checking and conditions hash generation.
|
|
5
|
-
class
|
|
6
|
-
attr_reader :base_behavior, :actions
|
|
5
|
+
class Rule # :nodoc:
|
|
6
|
+
attr_reader :base_behavior, :actions, :conditions
|
|
7
7
|
attr_writer :expanded_actions
|
|
8
8
|
|
|
9
9
|
# The first argument when initializing is the base_behavior which is a true/false
|
|
@@ -41,18 +41,6 @@ module CanCan
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def tableized_conditions(conditions = @conditions)
|
|
45
|
-
return conditions unless conditions.kind_of? Hash
|
|
46
|
-
conditions.inject({}) do |result_hash, (name, value)|
|
|
47
|
-
if value.kind_of? Hash
|
|
48
|
-
name = name.to_s.tableize.to_sym
|
|
49
|
-
value = tableized_conditions(value)
|
|
50
|
-
end
|
|
51
|
-
result_hash[name] = value
|
|
52
|
-
result_hash
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
44
|
def only_block?
|
|
57
45
|
conditions_empty? && !@block.nil?
|
|
58
46
|
end
|
|
@@ -100,19 +88,31 @@ module CanCan
|
|
|
100
88
|
@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)) }
|
|
101
89
|
end
|
|
102
90
|
|
|
91
|
+
# Checks if the given subject matches the given conditions hash.
|
|
92
|
+
# This behavior can be overriden by a model adapter by defining two class methods:
|
|
93
|
+
# override_matching_for_conditions?(subject, conditions) and
|
|
94
|
+
# matches_conditions_hash?(subject, conditions)
|
|
103
95
|
def matches_conditions_hash?(subject, conditions = @conditions)
|
|
104
|
-
conditions.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else
|
|
110
|
-
matches_conditions_hash? attribute, value
|
|
111
|
-
end
|
|
112
|
-
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
|
113
|
-
value.include? attribute
|
|
96
|
+
if conditions.empty?
|
|
97
|
+
true
|
|
98
|
+
else
|
|
99
|
+
if model_adapter(subject).override_conditions_hash_matching? subject, conditions
|
|
100
|
+
model_adapter(subject).matches_conditions_hash? subject, conditions
|
|
114
101
|
else
|
|
115
|
-
|
|
102
|
+
conditions.all? do |name, value|
|
|
103
|
+
attribute = subject.send(name)
|
|
104
|
+
if value.kind_of?(Hash)
|
|
105
|
+
if attribute.kind_of? Array
|
|
106
|
+
attribute.any? { |element| matches_conditions_hash? element, value }
|
|
107
|
+
else
|
|
108
|
+
matches_conditions_hash? attribute, value
|
|
109
|
+
end
|
|
110
|
+
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
|
111
|
+
value.include? attribute
|
|
112
|
+
else
|
|
113
|
+
attribute == value
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
end
|
|
@@ -129,5 +129,9 @@ module CanCan
|
|
|
129
129
|
@block.call(action, subject.class, subject, *extra_args)
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
|
+
|
|
133
|
+
def model_adapter(subject)
|
|
134
|
+
ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
|
135
|
+
end
|
|
132
136
|
end
|
|
133
137
|
end
|
data/lib/cancan.rb
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
require 'cancan/ability'
|
|
2
|
-
require 'cancan/
|
|
2
|
+
require 'cancan/rule'
|
|
3
3
|
require 'cancan/controller_resource'
|
|
4
4
|
require 'cancan/controller_additions'
|
|
5
|
-
require 'cancan/
|
|
5
|
+
require 'cancan/model_additions'
|
|
6
6
|
require 'cancan/exceptions'
|
|
7
|
-
require 'cancan/query'
|
|
8
7
|
require 'cancan/inherited_resource'
|
|
8
|
+
|
|
9
|
+
require 'cancan/model_adapters/abstract_adapter'
|
|
10
|
+
require 'cancan/model_adapters/default_adapter'
|
|
11
|
+
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
|
|
12
|
+
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
|
|
13
|
+
require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Ability
|
|
2
|
+
include CanCan::Ability
|
|
3
|
+
|
|
4
|
+
def initialize(user)
|
|
5
|
+
# Define abilities for the passed in user here. For example:
|
|
6
|
+
#
|
|
7
|
+
# user ||= User.new # guest user (not logged in)
|
|
8
|
+
# if user.admin?
|
|
9
|
+
# can :manage, :all
|
|
10
|
+
# else
|
|
11
|
+
# can :read, :all
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# The first argument to `can` is the action you are giving the user permission to do.
|
|
15
|
+
# If you pass :manage it will apply to every action. Other common actions here are
|
|
16
|
+
# :read, :create, :update and :destroy.
|
|
17
|
+
#
|
|
18
|
+
# The second argument is the resource the user can perform the action on. If you pass
|
|
19
|
+
# :all it will apply to every resource. Otherwise pass a Ruby class of the resource.
|
|
20
|
+
#
|
|
21
|
+
# The third argument is an optional hash of conditions to further filter the objects.
|
|
22
|
+
# For example, here the user can only update published articles.
|
|
23
|
+
#
|
|
24
|
+
# can :update, Article, :published => true
|
|
25
|
+
#
|
|
26
|
+
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
|
|
27
|
+
end
|
|
28
|
+
end
|
data/spec/README.rdoc
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
= CanCan Specs
|
|
2
|
+
|
|
3
|
+
== Running the specs
|
|
4
|
+
|
|
5
|
+
To run the specs first run the +bundle+ command to install the necessary gems and the +rake+ command to run the specs.
|
|
6
|
+
|
|
7
|
+
bundle
|
|
8
|
+
rake
|
|
9
|
+
|
|
10
|
+
The specs currently require Ruby 1.8.7. Ruby 1.9.2 support will be coming soon.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
== Model Adapters
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
MODEL_ADAPTER=data_mapper bundle
|
|
18
|
+
MODEL_ADAPTER=data_mapper rake
|
|
19
|
+
|
|
20
|
+
The different model adapters you can specify are:
|
|
21
|
+
|
|
22
|
+
* active_record (default)
|
|
23
|
+
* data_mapper
|
|
24
|
+
* mongoid
|
|
25
|
+
|
|
26
|
+
You can also run the +spec_all+ rake task to run specs for each adapter.
|
|
27
|
+
|
|
28
|
+
rake spec_all
|
data/spec/cancan/ability_spec.rb
CHANGED
|
@@ -32,7 +32,7 @@ describe CanCan::Ability do
|
|
|
32
32
|
@ability.can?(:read, Symbol).should be_true
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
it "should pass to previous
|
|
35
|
+
it "should pass to previous rule, if block returns false or nil" do
|
|
36
36
|
@ability.can :read, Symbol
|
|
37
37
|
@ability.can :read, Integer do |i|
|
|
38
38
|
i < 5
|
|
@@ -144,7 +144,7 @@ describe CanCan::Ability do
|
|
|
144
144
|
@ability.can?(:update, 123).should be_false
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
it "should support custom objects in the
|
|
147
|
+
it "should support custom objects in the rule" do
|
|
148
148
|
@ability.can :read, :stats
|
|
149
149
|
@ability.can?(:read, :stats).should be_true
|
|
150
150
|
@ability.can?(:update, :stats).should be_false
|
|
@@ -165,7 +165,7 @@ describe CanCan::Ability do
|
|
|
165
165
|
@ability.can?(:read, 123).should be_false
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
it "should pass to previous
|
|
168
|
+
it "should pass to previous rule, if block returns false or nil" do
|
|
169
169
|
@ability.can :read, :all
|
|
170
170
|
@ability.cannot :read, Integer do |int|
|
|
171
171
|
int > 10 ? nil : ( int > 5 )
|
|
@@ -343,6 +343,14 @@ describe CanCan::Ability do
|
|
|
343
343
|
end
|
|
344
344
|
end
|
|
345
345
|
|
|
346
|
+
it "should determine model adapter class by asking AbstractAdapter" do
|
|
347
|
+
model_class = Object.new
|
|
348
|
+
adapter_class = Object.new
|
|
349
|
+
stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
|
|
350
|
+
stub(adapter_class).new(model_class, []) { :adapter_instance }
|
|
351
|
+
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
|
352
|
+
end
|
|
353
|
+
|
|
346
354
|
describe "unauthorized message" do
|
|
347
355
|
after(:each) do
|
|
348
356
|
I18n.backend = nil
|
|
@@ -83,4 +83,34 @@ describe CanCan::ControllerAdditions do
|
|
|
83
83
|
stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
|
|
84
84
|
@controller.class.cancan_resource_class.should == CanCan::InheritedResource
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
it "cancan_skipper should be an empty hash with :authorize and :load options and remember changes" do
|
|
88
|
+
@controller_class.cancan_skipper.should == {:authorize => {}, :load => {}}
|
|
89
|
+
@controller_class.cancan_skipper[:load] = true
|
|
90
|
+
@controller_class.cancan_skipper[:load].should == true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "skip_authorize_resource should add itself to the cancan skipper with given model name and options" do
|
|
94
|
+
@controller_class.skip_authorize_resource(:project, :only => [:index, :show])
|
|
95
|
+
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
|
96
|
+
@controller_class.skip_authorize_resource(:only => [:index, :show])
|
|
97
|
+
@controller_class.cancan_skipper[:authorize][nil].should == {:only => [:index, :show]}
|
|
98
|
+
@controller_class.skip_authorize_resource(:article)
|
|
99
|
+
@controller_class.cancan_skipper[:authorize][:article].should == {}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "skip_load_resource should add itself to the cancan skipper with given model name and options" do
|
|
103
|
+
@controller_class.skip_load_resource(:project, :only => [:index, :show])
|
|
104
|
+
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
|
105
|
+
@controller_class.skip_load_resource(:only => [:index, :show])
|
|
106
|
+
@controller_class.cancan_skipper[:load][nil].should == {:only => [:index, :show]}
|
|
107
|
+
@controller_class.skip_load_resource(:article)
|
|
108
|
+
@controller_class.cancan_skipper[:load][:article].should == {}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "skip_load_and_authore_resource should add itself to the cancan skipper with given model name and options" do
|
|
112
|
+
@controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show])
|
|
113
|
+
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
|
114
|
+
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
|
115
|
+
end
|
|
86
116
|
end
|
|
@@ -3,10 +3,12 @@ require "spec_helper"
|
|
|
3
3
|
describe CanCan::ControllerResource do
|
|
4
4
|
before(:each) do
|
|
5
5
|
@params = HashWithIndifferentAccess.new(:controller => "projects")
|
|
6
|
-
@
|
|
6
|
+
@controller_class = Class.new
|
|
7
|
+
@controller = @controller_class.new
|
|
7
8
|
@ability = Ability.new(nil)
|
|
8
9
|
stub(@controller).params { @params }
|
|
9
10
|
stub(@controller).current_ability { @ability }
|
|
11
|
+
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
it "should load the resource into an instance variable if params[:id] is specified" do
|
|
@@ -91,6 +93,22 @@ describe CanCan::ControllerResource do
|
|
|
91
93
|
@controller.instance_variable_defined?(:@projects).should be_false
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
it "should not authorize single resource in collection action" do
|
|
97
|
+
@params[:action] = "index"
|
|
98
|
+
@controller.instance_variable_set(:@project, :some_project)
|
|
99
|
+
stub(@controller).authorize!(:index, Project) { raise CanCan::AccessDenied }
|
|
100
|
+
resource = CanCan::ControllerResource.new(@controller)
|
|
101
|
+
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should authorize parent resource in collection action" do
|
|
105
|
+
@params[:action] = "index"
|
|
106
|
+
@controller.instance_variable_set(:@category, :some_category)
|
|
107
|
+
stub(@controller).authorize!(:read, :some_category) { raise CanCan::AccessDenied }
|
|
108
|
+
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
|
|
109
|
+
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
|
110
|
+
end
|
|
111
|
+
|
|
94
112
|
it "should perform authorization using controller action and loaded model" do
|
|
95
113
|
@params[:action] = "show"
|
|
96
114
|
@controller.instance_variable_set(:@project, :some_project)
|
|
@@ -245,7 +263,7 @@ describe CanCan::ControllerResource do
|
|
|
245
263
|
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
|
246
264
|
category = Object.new
|
|
247
265
|
@controller.instance_variable_set(:@category, category)
|
|
248
|
-
stub(category).build_project { Project.new }
|
|
266
|
+
stub(category).build_project { |attributes| Project.new(attributes) }
|
|
249
267
|
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
|
|
250
268
|
resource.load_resource
|
|
251
269
|
@controller.instance_variable_get(:@project).name.should == "foobar"
|
|
@@ -323,4 +341,52 @@ describe CanCan::ControllerResource do
|
|
|
323
341
|
CanCan::ControllerResource.new(@controller, :nested => :project)
|
|
324
342
|
}.should raise_error(CanCan::ImplementationRemoved)
|
|
325
343
|
end
|
|
344
|
+
|
|
345
|
+
it "should skip resource behavior for :only actions in array" do
|
|
346
|
+
stub(@controller_class).cancan_skipper { {:load => {nil => {:only => [:index, :show]}}} }
|
|
347
|
+
@params.merge!(:action => "index")
|
|
348
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
|
349
|
+
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
|
350
|
+
@params.merge!(:action => "show")
|
|
351
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
|
352
|
+
@params.merge!(:action => "other_action")
|
|
353
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it "should skip resource behavior for :only one action on resource" do
|
|
357
|
+
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:only => :index}}} }
|
|
358
|
+
@params.merge!(:action => "index")
|
|
359
|
+
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
|
360
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
|
361
|
+
@params.merge!(:action => "other_action")
|
|
362
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it "should skip resource behavior :except actions in array" do
|
|
366
|
+
stub(@controller_class).cancan_skipper { {:load => {nil => {:except => [:index, :show]}}} }
|
|
367
|
+
@params.merge!(:action => "index")
|
|
368
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
|
369
|
+
@params.merge!(:action => "show")
|
|
370
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
|
371
|
+
@params.merge!(:action => "other_action")
|
|
372
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
|
373
|
+
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
it "should skip resource behavior :except one action on resource" do
|
|
377
|
+
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:except => :index}}} }
|
|
378
|
+
@params.merge!(:action => "index")
|
|
379
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
|
380
|
+
@params.merge!(:action => "other_action")
|
|
381
|
+
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
|
382
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
it "should skip loading and authorization" do
|
|
386
|
+
stub(@controller_class).cancan_skipper { {:authorize => {nil => {}}, :load => {nil => {}}} }
|
|
387
|
+
@params.merge!(:action => "new")
|
|
388
|
+
resource = CanCan::ControllerResource.new(@controller)
|
|
389
|
+
lambda { resource.load_and_authorize_resource }.should_not raise_error
|
|
390
|
+
@controller.instance_variable_get(:@project).should be_nil
|
|
391
|
+
end
|
|
326
392
|
end
|
|
@@ -3,10 +3,12 @@ require "spec_helper"
|
|
|
3
3
|
describe CanCan::InheritedResource do
|
|
4
4
|
before(:each) do
|
|
5
5
|
@params = HashWithIndifferentAccess.new(:controller => "projects")
|
|
6
|
-
@
|
|
6
|
+
@controller_class = Class.new
|
|
7
|
+
@controller = @controller_class.new
|
|
7
8
|
@ability = Ability.new(nil)
|
|
8
9
|
stub(@controller).params { @params }
|
|
9
10
|
stub(@controller).current_ability { @ability }
|
|
11
|
+
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
it "show should load resource through @controller.resource" do
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
RSpec.configure do |config|
|
|
5
|
+
config.extend WithModel
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
|
9
|
+
|
|
10
|
+
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
|
11
|
+
with_model :article do
|
|
12
|
+
table do |t|
|
|
13
|
+
t.boolean "published"
|
|
14
|
+
t.boolean "secret"
|
|
15
|
+
end
|
|
16
|
+
model do
|
|
17
|
+
has_many :comments
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
with_model :comment do
|
|
22
|
+
table do |t|
|
|
23
|
+
t.boolean "spam"
|
|
24
|
+
t.integer "article_id"
|
|
25
|
+
end
|
|
26
|
+
model do
|
|
27
|
+
belongs_to :article
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
before(:each) do
|
|
32
|
+
Article.delete_all
|
|
33
|
+
Comment.delete_all
|
|
34
|
+
@ability = Object.new
|
|
35
|
+
@ability.extend(CanCan::Ability)
|
|
36
|
+
@article_table = Article.table_name
|
|
37
|
+
@comment_table = Comment.table_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should be for only active record classes" do
|
|
41
|
+
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
|
|
42
|
+
CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
|
|
43
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should not fetch any records when no abilities are defined" do
|
|
47
|
+
Article.create!
|
|
48
|
+
Article.accessible_by(@ability).should be_empty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should fetch all articles when one can read all" do
|
|
52
|
+
@ability.can :read, Article
|
|
53
|
+
article = Article.create!
|
|
54
|
+
Article.accessible_by(@ability).should == [article]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should fetch only the articles that are published" do
|
|
58
|
+
@ability.can :read, Article, :published => true
|
|
59
|
+
article1 = Article.create!(:published => true)
|
|
60
|
+
article2 = Article.create!(:published => false)
|
|
61
|
+
Article.accessible_by(@ability).should == [article1]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should fetch any articles which are published or secret" do
|
|
65
|
+
@ability.can :read, Article, :published => true
|
|
66
|
+
@ability.can :read, Article, :secret => true
|
|
67
|
+
article1 = Article.create!(:published => true, :secret => false)
|
|
68
|
+
article2 = Article.create!(:published => true, :secret => true)
|
|
69
|
+
article3 = Article.create!(:published => false, :secret => true)
|
|
70
|
+
article4 = Article.create!(:published => false, :secret => false)
|
|
71
|
+
Article.accessible_by(@ability).should == [article1, article2, article3]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should fetch only the articles that are published and not secret" do
|
|
75
|
+
@ability.can :read, Article, :published => true
|
|
76
|
+
@ability.cannot :read, Article, :secret => true
|
|
77
|
+
article1 = Article.create!(:published => true, :secret => false)
|
|
78
|
+
article2 = Article.create!(:published => true, :secret => true)
|
|
79
|
+
article3 = Article.create!(:published => false, :secret => true)
|
|
80
|
+
article4 = Article.create!(:published => false, :secret => false)
|
|
81
|
+
Article.accessible_by(@ability).should == [article1]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should only read comments for articles which are published" do
|
|
85
|
+
@ability.can :read, Comment, :article => { :published => true }
|
|
86
|
+
comment1 = Comment.create!(:article => Article.create!(:published => true))
|
|
87
|
+
comment2 = Comment.create!(:article => Article.create!(:published => false))
|
|
88
|
+
Comment.accessible_by(@ability).should == [comment1]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "should allow conditions in SQL and merge with hash conditions" do
|
|
92
|
+
@ability.can :read, Article, :published => true
|
|
93
|
+
@ability.can :read, Article, ["secret=?", true]
|
|
94
|
+
article1 = Article.create!(:published => true, :secret => false)
|
|
95
|
+
article4 = Article.create!(:published => false, :secret => false)
|
|
96
|
+
Article.accessible_by(@ability).should == [article1]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should not allow to fetch records when ability with just block present" do
|
|
100
|
+
@ability.can :read, Article do
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "should not allow to check ability on object against SQL conditions without block" do
|
|
107
|
+
@ability.can :read, Article, ["secret=?", true]
|
|
108
|
+
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should have false conditions if no abilities match" do
|
|
112
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "should return false conditions for cannot clause" do
|
|
116
|
+
@ability.cannot :read, Article
|
|
117
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "should return SQL for single `can` definition in front of default `cannot` condition" do
|
|
121
|
+
@ability.cannot :read, Article
|
|
122
|
+
@ability.can :read, Article, :published => false, :secret => true
|
|
123
|
+
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "should return true condition for single `can` definition in front of default `can` condition" do
|
|
127
|
+
@ability.can :read, Article
|
|
128
|
+
@ability.can :read, Article, :published => false, :secret => true
|
|
129
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
|
133
|
+
@ability.cannot :read, Article
|
|
134
|
+
@ability.cannot :read, Article, :published => false, :secret => true
|
|
135
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
|
139
|
+
@ability.can :read, Article
|
|
140
|
+
@ability.cannot :read, Article, :published => false, :secret => true
|
|
141
|
+
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "should return appropriate sql conditions in complex case" do
|
|
145
|
+
@ability.can :read, Article
|
|
146
|
+
@ability.can :manage, Article, :id => 1
|
|
147
|
+
@ability.can :update, Article, :published => true
|
|
148
|
+
@ability.cannot :update, Article, :secret => true
|
|
149
|
+
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
|
|
150
|
+
@ability.model_adapter(Article, :manage).conditions.should == {:id => 1}
|
|
151
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "should not forget conditions when calling with SQL string" do
|
|
155
|
+
@ability.can :read, Article, :published => true
|
|
156
|
+
@ability.can :read, Article, ['secret=?', false]
|
|
157
|
+
adapter = @ability.model_adapter(Article, :read)
|
|
158
|
+
2.times do
|
|
159
|
+
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "should have nil joins if no rules" do
|
|
164
|
+
@ability.model_adapter(Article, :read).joins.should be_nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "should have nil joins if no nested hashes specified in conditions" do
|
|
168
|
+
@ability.can :read, Article, :published => false
|
|
169
|
+
@ability.can :read, Article, :secret => true
|
|
170
|
+
@ability.model_adapter(Article, :read).joins.should be_nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "should merge separate joins into a single array" do
|
|
174
|
+
@ability.can :read, Article, :project => { :blocked => false }
|
|
175
|
+
@ability.can :read, Article, :company => { :admin => true }
|
|
176
|
+
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "should merge same joins into a single array" do
|
|
180
|
+
@ability.can :read, Article, :project => { :blocked => false }
|
|
181
|
+
@ability.can :read, Article, :project => { :admin => true }
|
|
182
|
+
@ability.model_adapter(Article, :read).joins.should == [:project]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|