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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59a95c6a9629c67f5004699cdbb205108d814ad5
4
- data.tar.gz: 4f9cd96fcf1d2a3519b07605619240403b6b33eb
3
+ metadata.gz: 597f9c67d00dc9af7805456aca9464c41b795f42
4
+ data.tar.gz: e52df51e0f69a1f98509e7d8ae7ab8e14affb418
5
5
  SHA512:
6
- metadata.gz: da1ce9db915cc5305d31d88c0c8f437088ceeab1f2b744a3a258923d59b6da743dd86a643a39c4602afe313c13cb39f1361d220144086a05913c41f5398428ed
7
- data.tar.gz: 09c524f8d9928aa7029a9814fae9d45dcd8bf27ecf52f756cbafd02a716bc599f0ab12b4a3fdb37a71aa7ab5893b734b313989f1450a5d3792b77c9abdf7890a
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
@@ -2,6 +2,8 @@ Develop
2
2
 
3
3
  Unreleased
4
4
 
5
+ * Add support for rails 4 enum's (markpmitchell)
6
+
5
7
  1.13.1 (Oct 8th, 2015)
6
8
 
7
9
  * Fix #merge with empty Ability (jhawthorn)
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.png?branch=master)](https://travis-ci.org/CanCanCommunity/cancancan)
5
- [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.png)](https://codeclimate.com/github/CanCanCommunity/cancancan)
6
- [![Inch CI](http://inch-ci.org/github/CanCanCommunity/cancancan.png)](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 or Problems?
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
- If you have any issues with CanCan 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 add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request.
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
@@ -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
- subject = extract_subjects(subject)
65
-
66
- match = subject.map do |subject|
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.compact.first
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
- subject = if subject.kind_of?(Hash) && subject.key?(:any)
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.camelize].flatten.map(&:camelize).join('::').singularize.constantize
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
- return adapter.matches_condition?(subject, name, value)
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, child = subject_hash.first
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
@@ -1,3 +1,3 @@
1
1
  module CanCan
2
- VERSION = "1.13.1"
2
+ VERSION = "1.14.0"
3
3
  end
data/spec/README.rdoc CHANGED
@@ -16,7 +16,7 @@ You can then run all test sets:
16
16
 
17
17
  Or individual ones:
18
18
 
19
- appraisal rails_3.0 rake
19
+ appraisal activerecord_3.2 rake
20
20
 
21
21
  A list of the tests is in the +Appraisal+ file.
22
22
 
@@ -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.13.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: 2015-10-08 00:00:00.000000000 Z
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.2.3
154
+ rubygems_version: 2.5.1
155
155
  signing_key:
156
156
  specification_version: 4
157
157
  summary: Simple authorization solution for Rails.