cancancan 1.13.1 → 1.14.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -4
- data/CHANGELOG.rdoc +2 -0
- data/README.md +8 -5
- data/lib/cancan/ability.rb +20 -8
- data/lib/cancan/controller_resource.rb +1 -3
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +17 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +23 -2
- data/lib/cancan/rule.rb +5 -4
- data/lib/cancan/version.rb +1 -1
- data/spec/README.rdoc +1 -1
- data/spec/cancan/ability_spec.rb +23 -0
- data/spec/cancan/controller_resource_spec.rb +11 -0
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +69 -0
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +21 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +20 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 597f9c67d00dc9af7805456aca9464c41b795f42
|
4
|
+
data.tar.gz: e52df51e0f69a1f98509e7d8ae7ab8e14affb418
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df305f2d2c391e2ce49bf98f0cdbe6b0cc0d96140467bb3efa66032ce61fe0ada2984ab94bfc956fbd5a8e3d1251ae7f64e3fe5773a815d172abb62f5f71db18
|
7
|
+
data.tar.gz: 33dcb0f4db08c07cf7e62103588177c628ddf7f8e219495b2e74bec94c9e2608c9b38daf2009626ef51aa77e97f6cdfc4c12d08a946f2c41d2b99e7385a8e7ea
|
data/.travis.yml
CHANGED
@@ -5,8 +5,7 @@ rvm:
|
|
5
5
|
- 2.0.0
|
6
6
|
- 2.1.0
|
7
7
|
- 2.2.0
|
8
|
-
- jruby
|
9
|
-
- rbx
|
8
|
+
- jruby-9.0.5.0
|
10
9
|
gemfile:
|
11
10
|
- gemfiles/activerecord_3.2.gemfile
|
12
11
|
- gemfiles/activerecord_4.0.gemfile
|
@@ -18,8 +17,6 @@ services:
|
|
18
17
|
- mongodb
|
19
18
|
matrix:
|
20
19
|
fast_finish: true
|
21
|
-
allow_failures:
|
22
|
-
- rvm: rbx
|
23
20
|
exclude:
|
24
21
|
- rvm: 2.2.0
|
25
22
|
gemfile: gemfiles/activerecord_3.2.gemfile
|
data/CHANGELOG.rdoc
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# CanCanCan
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/cancancan)
|
4
|
-
[](https://travis-ci.org/CanCanCommunity/cancancan)
|
5
|
+
[](https://codeclimate.com/github/CanCanCommunity/cancancan)
|
6
|
+
[](http://inch-ci.org/github/CanCanCommunity/cancancan)
|
7
7
|
|
8
8
|
[Wiki](https://github.com/CanCanCommunity/cancancan/wiki) | [RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | [Screencast](http://railscasts.com/episodes/192-authorization-with-cancan) | [IRC: #cancancan (freenode)](http://webchat.freenode.net/?channels=cancancan)
|
9
9
|
|
@@ -193,9 +193,12 @@ This will raise an exception if authorization is not performed in an action. If
|
|
193
193
|
* [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/Changing-Defaults)
|
194
194
|
* [See more](https://github.com/CanCanCommunity/cancancan/wiki)
|
195
195
|
|
196
|
-
## Questions
|
196
|
+
## Questions?
|
197
|
+
If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the [documentation](https://github.com/CanCanCommunity/cancancan/wiki) or our [mailing list](http://groups.google.com/group/cancancan), please [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag [cancancan](http://stackoverflow.com/questions/tagged/cancancan)
|
197
198
|
|
198
|
-
|
199
|
+
## Bugs?
|
200
|
+
|
201
|
+
If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request.
|
199
202
|
|
200
203
|
|
201
204
|
## Development
|
data/lib/cancan/ability.rb
CHANGED
@@ -61,14 +61,11 @@ module CanCan
|
|
61
61
|
#
|
62
62
|
# Also see the RSpec Matchers to aid in testing.
|
63
63
|
def can?(action, subject, *extra_args)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
relevant_rules_for_match(action, subject).detect do |rule|
|
68
|
-
rule.matches_conditions?(action, subject, extra_args)
|
64
|
+
match = extract_subjects(subject).lazy.map do |a_subject|
|
65
|
+
relevant_rules_for_match(action, a_subject).detect do |rule|
|
66
|
+
rule.matches_conditions?(action, a_subject, extra_args)
|
69
67
|
end
|
70
|
-
end.
|
71
|
-
|
68
|
+
end.reject(&:nil?).first
|
72
69
|
match ? match.base_behavior : false
|
73
70
|
end
|
74
71
|
# Convenience method which works the same as "can?" but returns the opposite value.
|
@@ -322,7 +319,7 @@ module CanCan
|
|
322
319
|
|
323
320
|
# It translates to an array the subject or the hash with multiple subjects given to can?.
|
324
321
|
def extract_subjects(subject)
|
325
|
-
|
322
|
+
if subject.kind_of?(Hash) && subject.key?(:any)
|
326
323
|
subject[:any]
|
327
324
|
else
|
328
325
|
[subject]
|
@@ -369,9 +366,24 @@ module CanCan
|
|
369
366
|
rule.relevant? action, subject
|
370
367
|
end
|
371
368
|
relevant.reverse!.uniq!
|
369
|
+
optimize_order! relevant
|
372
370
|
relevant
|
373
371
|
end
|
374
372
|
|
373
|
+
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
374
|
+
def optimize_order!(rules)
|
375
|
+
first_can_in_group = -1
|
376
|
+
rules.each_with_index do |rule, i|
|
377
|
+
(first_can_in_group = -1) and next unless rule.base_behavior
|
378
|
+
(first_can_in_group = i) and next if first_can_in_group == -1
|
379
|
+
if rule.subjects == [:all]
|
380
|
+
rules[i] = rules[first_can_in_group]
|
381
|
+
rules[first_can_in_group] = rule
|
382
|
+
first_can_in_group += 1
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
375
387
|
def possible_relevant_rules(subject)
|
376
388
|
if subject.is_a?(Hash)
|
377
389
|
rules
|
@@ -262,9 +262,7 @@ module CanCan
|
|
262
262
|
end
|
263
263
|
|
264
264
|
def namespaced_name
|
265
|
-
[namespace, name
|
266
|
-
rescue NameError
|
267
|
-
name
|
265
|
+
([namespace, name] * '/').singularize.camelize.safe_constantize || name
|
268
266
|
end
|
269
267
|
|
270
268
|
def name_from_controller
|
@@ -18,6 +18,23 @@ module CanCan
|
|
18
18
|
relation
|
19
19
|
end
|
20
20
|
|
21
|
+
def self.override_condition_matching?(subject, name, value)
|
22
|
+
# ActiveRecord introduced enums in version 4.1.
|
23
|
+
ActiveRecord::VERSION::MINOR >= 1 &&
|
24
|
+
subject.class.defined_enums.include?(name.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.matches_condition?(subject, name, value)
|
28
|
+
# Get the mapping from enum strings to values.
|
29
|
+
enum = subject.class.send(name.to_s.pluralize)
|
30
|
+
# Get the value of the attribute as an integer.
|
31
|
+
attribute = enum[subject.send(name)]
|
32
|
+
# Check to see if the value matches the condition.
|
33
|
+
value.is_a?(Enumerable) ?
|
34
|
+
(value.include? attribute) :
|
35
|
+
attribute == value
|
36
|
+
end
|
37
|
+
|
21
38
|
# Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
|
22
39
|
def sanitize_sql(conditions)
|
23
40
|
if ActiveRecord::VERSION::MINOR >= 2 && Hash === conditions
|
@@ -35,15 +35,36 @@ module CanCan
|
|
35
35
|
|
36
36
|
rules.inject(@model_class.all) do |records, rule|
|
37
37
|
if process_can_rules && rule.base_behavior
|
38
|
-
records.or rule.conditions
|
38
|
+
records.or simplify_relations(@model_class, rule.conditions)
|
39
39
|
elsif !rule.base_behavior
|
40
|
-
records.excludes rule.conditions
|
40
|
+
records.excludes simplify_relations(@model_class, rule.conditions)
|
41
41
|
else
|
42
42
|
records
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Look for criteria on relations and replace with simple id queries
|
50
|
+
# eg.
|
51
|
+
# {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}}
|
52
|
+
# {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }}
|
53
|
+
def simplify_relations model_class, conditions
|
54
|
+
model_relations = model_class.relations.with_indifferent_access
|
55
|
+
Hash[
|
56
|
+
conditions.map {|k,v|
|
57
|
+
if relation = model_relations[k]
|
58
|
+
relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name]
|
59
|
+
v = simplify_relations(relation_class_name.constantize, v)
|
60
|
+
relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id)
|
61
|
+
k = "#{k}_id"
|
62
|
+
v = { "$in" => relation_ids }
|
63
|
+
end
|
64
|
+
[k,v]
|
65
|
+
}
|
66
|
+
]
|
67
|
+
end
|
47
68
|
end
|
48
69
|
end
|
49
70
|
end
|
data/lib/cancan/rule.rb
CHANGED
@@ -112,15 +112,15 @@ module CanCan
|
|
112
112
|
|
113
113
|
conditions.all? do |name, value|
|
114
114
|
if adapter.override_condition_matching?(subject, name, value)
|
115
|
-
|
115
|
+
adapter.matches_condition?(subject, name, value)
|
116
|
+
else
|
117
|
+
condition_match?(subject.send(name), value)
|
116
118
|
end
|
117
|
-
|
118
|
-
condition_match?(subject.send(name), value)
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
122
|
def nested_subject_matches_conditions?(subject_hash)
|
123
|
-
parent,
|
123
|
+
parent, _child = subject_hash.first
|
124
124
|
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
125
125
|
end
|
126
126
|
|
@@ -140,6 +140,7 @@ module CanCan
|
|
140
140
|
case value
|
141
141
|
when Hash then hash_condition_match?(attribute, value)
|
142
142
|
when String then attribute == value
|
143
|
+
when Range then value.cover?(attribute)
|
143
144
|
when Enumerable then value.include?(attribute)
|
144
145
|
else attribute == value
|
145
146
|
end
|
data/lib/cancan/version.rb
CHANGED
data/spec/README.rdoc
CHANGED
data/spec/cancan/ability_spec.rb
CHANGED
@@ -52,6 +52,22 @@ describe CanCan::Ability do
|
|
52
52
|
expect(@ability.can?(:read, 6)).to be(true)
|
53
53
|
end
|
54
54
|
|
55
|
+
it "performs can(_, :all) before other checks when can(_, :all) is defined before" do
|
56
|
+
@ability.can :manage, :all
|
57
|
+
@ability.can :edit, String do |_string|
|
58
|
+
fail 'Performed a check for :edit before the check for :all'
|
59
|
+
end
|
60
|
+
expect { @ability.can? :edit, 'a' }.to_not raise_exception
|
61
|
+
end
|
62
|
+
|
63
|
+
it "performs can(_, :all) before other checks when can(_, :all) is defined after" do
|
64
|
+
@ability.can :edit, String do |_string|
|
65
|
+
fail 'Performed a check for :edit before the check for :all'
|
66
|
+
end
|
67
|
+
@ability.can :manage, :all
|
68
|
+
expect { @ability.can? :edit, 'a' }.to_not raise_exception
|
69
|
+
end
|
70
|
+
|
55
71
|
it "does not pass class with object if :all objects are accepted" do
|
56
72
|
@ability.can :preview, :all do |object|
|
57
73
|
expect(object).to eq(123)
|
@@ -294,6 +310,13 @@ describe CanCan::Ability do
|
|
294
310
|
expect(@ability.can?(:read, 4..40)).to be(false)
|
295
311
|
end
|
296
312
|
|
313
|
+
it "allows a range of time in conditions hash" do
|
314
|
+
@ability.can :read, Range, :begin => 1.day.from_now..3.days.from_now
|
315
|
+
expect(@ability.can?(:read, 1.day.from_now..10.days.from_now)).to be(true)
|
316
|
+
expect(@ability.can?(:read, 2.days.from_now..20.days.from_now)).to be(true)
|
317
|
+
expect(@ability.can?(:read, 4.days.from_now..40.days.from_now)).to be(false)
|
318
|
+
end
|
319
|
+
|
297
320
|
it "allows nested hashes in conditions hash" do
|
298
321
|
@ability.can :read, Range, :begin => { :to_i => 5 }
|
299
322
|
expect(@ability.can?(:read, 5..7)).to be(true)
|
@@ -501,6 +501,17 @@ describe CanCan::ControllerResource do
|
|
501
501
|
end
|
502
502
|
end
|
503
503
|
|
504
|
+
context 'when @name passed as symbol' do
|
505
|
+
it 'returns namespaced #resource_class' do
|
506
|
+
module Admin; end
|
507
|
+
class Admin::Dashboard; end;
|
508
|
+
params.merge!(:controller => "admin/dashboard")
|
509
|
+
resource = CanCan::ControllerResource.new(controller, :dashboard)
|
510
|
+
|
511
|
+
expect(resource.send(:resource_class)).to eq Admin::Dashboard
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
504
515
|
it "calls the santitizer when the parameter hash matches our object" do
|
505
516
|
params.merge!(:action => 'create', :model => { :name => 'test' })
|
506
517
|
allow(controller).to receive(:create_params).and_return({})
|
@@ -37,6 +37,75 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
37
37
|
|
38
38
|
expect(Parent.accessible_by(@ability).order(:created_at => :asc).includes(:children).first.children).to eq [child2, child1]
|
39
39
|
end
|
40
|
+
|
41
|
+
if ActiveRecord::VERSION::MINOR >= 1
|
42
|
+
it "allows filters on enums" do
|
43
|
+
ActiveRecord::Schema.define do
|
44
|
+
create_table(:shapes) do |t|
|
45
|
+
t.integer :color, default: 0, null: false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Shape < ActiveRecord::Base
|
50
|
+
enum color: [:red, :green, :blue]
|
51
|
+
end
|
52
|
+
|
53
|
+
red = Shape.create!(color: :red)
|
54
|
+
green = Shape.create!(color: :green)
|
55
|
+
blue = Shape.create!(color: :blue)
|
56
|
+
|
57
|
+
# A condition with a single value.
|
58
|
+
@ability.can :read, Shape, color: Shape.colors[:green]
|
59
|
+
|
60
|
+
expect(@ability.cannot? :read, red).to be true
|
61
|
+
expect(@ability.can? :read, green).to be true
|
62
|
+
expect(@ability.cannot? :read, blue).to be true
|
63
|
+
|
64
|
+
accessible = Shape.accessible_by(@ability)
|
65
|
+
expect(accessible).to contain_exactly(green)
|
66
|
+
|
67
|
+
# A condition with multiple values.
|
68
|
+
@ability.can :update, Shape, color: [Shape.colors[:red],
|
69
|
+
Shape.colors[:blue]]
|
70
|
+
|
71
|
+
expect(@ability.can? :update, red).to be true
|
72
|
+
expect(@ability.cannot? :update, green).to be true
|
73
|
+
expect(@ability.can? :update, blue).to be true
|
74
|
+
|
75
|
+
accessible = Shape.accessible_by(@ability, :update)
|
76
|
+
expect(accessible).to contain_exactly(red, blue)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "allows dual filter on enums" do
|
80
|
+
ActiveRecord::Schema.define do
|
81
|
+
create_table(:discs) do |t|
|
82
|
+
t.integer :color, default: 0, null: false
|
83
|
+
t.integer :shape, default: 3, null: false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Disc < ActiveRecord::Base
|
88
|
+
enum color: [:red, :green, :blue]
|
89
|
+
enum shape: { triangle: 3, rectangle: 4 }
|
90
|
+
end
|
91
|
+
|
92
|
+
red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle])
|
93
|
+
green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle])
|
94
|
+
green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle])
|
95
|
+
blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle])
|
96
|
+
|
97
|
+
# A condition with a dual filter.
|
98
|
+
@ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle]
|
99
|
+
|
100
|
+
expect(@ability.cannot? :read, red_triangle).to be true
|
101
|
+
expect(@ability.cannot? :read, green_triangle).to be true
|
102
|
+
expect(@ability.can? :read, green_rectangle).to be true
|
103
|
+
expect(@ability.cannot? :read, blue_rectangle).to be true
|
104
|
+
|
105
|
+
accessible = Disc.accessible_by(@ability)
|
106
|
+
expect(accessible).to contain_exactly(green_rectangle)
|
107
|
+
end
|
108
|
+
end
|
40
109
|
end
|
41
110
|
|
42
111
|
if Gem::Specification.find_all_by_name('pg').any?
|
@@ -380,5 +380,26 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
380
380
|
expect(Namespace::TableX.accessible_by(ability)).to eq([table_x])
|
381
381
|
end
|
382
382
|
end
|
383
|
+
|
384
|
+
context 'when conditions are non iterable ranges' do
|
385
|
+
before :each do
|
386
|
+
ActiveRecord::Schema.define do
|
387
|
+
create_table( :courses ) do |t|
|
388
|
+
t.datetime :start_at
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
class Course < ActiveRecord::Base
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'fetches only the valid records' do
|
397
|
+
@ability.can :read, Course, :start_at => 1.day.ago..1.day.from_now
|
398
|
+
Course.create!(:start_at => 10.days.ago)
|
399
|
+
valid_course = Course.create!(:start_at => Time.now)
|
400
|
+
|
401
|
+
expect(Course.accessible_by(@ability)).to eq([valid_course])
|
402
|
+
end
|
403
|
+
end
|
383
404
|
end
|
384
405
|
end
|
@@ -12,6 +12,13 @@ if defined? CanCan::ModelAdapters::MongoidAdapter
|
|
12
12
|
include Mongoid::Document
|
13
13
|
|
14
14
|
referenced_in :mongoid_category
|
15
|
+
references_many :mongoid_sub_projects
|
16
|
+
end
|
17
|
+
|
18
|
+
class MongoidSubProject
|
19
|
+
include Mongoid::Document
|
20
|
+
|
21
|
+
referenced_in :mongoid_project
|
15
22
|
end
|
16
23
|
|
17
24
|
Mongoid.configure do |config|
|
@@ -222,6 +229,19 @@ if defined? CanCan::ModelAdapters::MongoidAdapter
|
|
222
229
|
MongoidProject.accessible_by(@ability)
|
223
230
|
}.to raise_error(CanCan::Error)
|
224
231
|
end
|
232
|
+
|
233
|
+
it "can handle nested queries for accessible_by" do
|
234
|
+
@ability.can :read, MongoidSubProject, {:mongoid_project => {:mongoid_category => { :name => 'Authorization'}}}
|
235
|
+
cat1 = MongoidCategory.create name: 'Authentication'
|
236
|
+
cat2 = MongoidCategory.create name: 'Authorization'
|
237
|
+
proj1 = cat1.mongoid_projects.create name: 'Proj1'
|
238
|
+
proj2 = cat2.mongoid_projects.create name: 'Proj2'
|
239
|
+
sub1 = proj1.mongoid_sub_projects.create name: 'Sub1'
|
240
|
+
sub2 = proj2.mongoid_sub_projects.create name: 'Sub2'
|
241
|
+
expect(MongoidSubProject.accessible_by(@ability)).to match_array([sub1])
|
242
|
+
end
|
243
|
+
|
244
|
+
|
225
245
|
end
|
226
246
|
end
|
227
247
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cancancan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Rite
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2016-05-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
151
|
version: '0'
|
152
152
|
requirements: []
|
153
153
|
rubyforge_project:
|
154
|
-
rubygems_version: 2.
|
154
|
+
rubygems_version: 2.5.1
|
155
155
|
signing_key:
|
156
156
|
specification_version: 4
|
157
157
|
summary: Simple authorization solution for Rails.
|