cancancan 1.13.1 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/cancancan.svg)](http://badge.fury.io/rb/cancancan)
|
4
|
-
[![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.
|
5
|
-
[![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.
|
6
|
-
[![Inch CI](http://inch-ci.org/github/CanCanCommunity/cancancan.
|
4
|
+
[![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.svg?branch=develop)](https://travis-ci.org/CanCanCommunity/cancancan)
|
5
|
+
[![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan)
|
6
|
+
[![Inch CI](http://inch-ci.org/github/CanCanCommunity/cancancan.svg)](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.
|