cancannible 0.0.1
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 +15 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +11 -0
- data/cancannible.gemspec +31 -0
- data/lib/cancannible.rb +8 -0
- data/lib/cancannible/ability_preload_adapter.rb +15 -0
- data/lib/cancannible/base.rb +213 -0
- data/lib/cancannible/config.rb +26 -0
- data/lib/cancannible/version.rb +3 -0
- data/lib/generators/cancannible/install_generator.rb +39 -0
- data/lib/generators/cancannible/templates/cancannible_initializer.rb +97 -0
- data/lib/generators/cancannible/templates/migration.rb +15 -0
- data/lib/generators/cancannible/templates/permission.rb +8 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/ability.rb +5 -0
- data/spec/support/migrations_helper.rb +61 -0
- data/spec/support/models.rb +45 -0
- data/spec/unit/base_spec.rb +152 -0
- data/spec/unit/cached_abilities_spec.rb +67 -0
- data/spec/unit/config_spec.rb +17 -0
- data/spec/unit/custom_refinements_spec.rb +223 -0
- data/spec/unit/inherited_permissions_spec.rb +145 -0
- metadata +207 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cancannible do
|
4
|
+
let(:grantee_class) { Member }
|
5
|
+
let(:grantee) { grantee_class.create! }
|
6
|
+
|
7
|
+
describe "#abilities" do
|
8
|
+
subject { grantee.abilities }
|
9
|
+
|
10
|
+
context "when get_cached_abilities provided" do
|
11
|
+
before do
|
12
|
+
Cancannible.get_cached_abilities = proc{|grantee| "get_cached_abilities for #{grantee.id}" }
|
13
|
+
end
|
14
|
+
it "should returned the cached object" do
|
15
|
+
should eql("get_cached_abilities for #{grantee.id}")
|
16
|
+
end
|
17
|
+
context "unless reload requested" do
|
18
|
+
subject { grantee.abilities(true) }
|
19
|
+
it { should be_an(Ability) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when store_cached_abilities provided" do
|
24
|
+
before do
|
25
|
+
@stored = nil
|
26
|
+
Cancannible.store_cached_abilities = proc{ |grantee,ability| @stored = { grantee_id: grantee.id, ability: ability } }
|
27
|
+
end
|
28
|
+
it "should store the cached object" do
|
29
|
+
expect { subject }.to change { @stored }.from(nil)
|
30
|
+
expect(@stored[:grantee_id]).to eql(grantee.id)
|
31
|
+
expect(@stored[:ability]).to be_an(Ability)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when get and store cached_abilities provided" do
|
36
|
+
before do
|
37
|
+
@stored = nil
|
38
|
+
@store = 0
|
39
|
+
Cancannible.get_cached_abilities = proc{|grantee| @stored }
|
40
|
+
Cancannible.store_cached_abilities = proc{ |grantee,ability| @store += 1 ; @stored = { grantee_id: grantee.id, ability: ability } }
|
41
|
+
end
|
42
|
+
it "should store the cached object on the first call" do
|
43
|
+
expect { subject }.to change { @stored }.from(nil)
|
44
|
+
expect(@store).to eql(1)
|
45
|
+
expect(@stored[:grantee_id]).to eql(grantee.id)
|
46
|
+
expect(@stored[:ability]).to be_an(Ability)
|
47
|
+
end
|
48
|
+
it "should return the cached object on the second call" do
|
49
|
+
expect { subject }.to change { @stored }.from(nil)
|
50
|
+
expect(@store).to eql(1)
|
51
|
+
expect { grantee.abilities }.to_not change { @store }
|
52
|
+
expect(grantee.abilities[:grantee_id]).to eql(grantee.id)
|
53
|
+
expect(grantee.abilities[:ability]).to be_an(Ability)
|
54
|
+
end
|
55
|
+
it "should re-cache object on the second call if refresh requested" do
|
56
|
+
expect { subject }.to change { @stored }.from(nil)
|
57
|
+
expect(@store).to eql(1)
|
58
|
+
expect { grantee.abilities(true) }.to change { @store }.from(1).to(2)
|
59
|
+
expect(grantee.abilities[:grantee_id]).to eql(grantee.id)
|
60
|
+
expect(grantee.abilities[:ability]).to be_an(Ability)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cancannible do
|
4
|
+
let(:grantee_class) { Member }
|
5
|
+
let(:grantee) { grantee_class.create! }
|
6
|
+
|
7
|
+
describe "#get_cached_abilities" do
|
8
|
+
let(:flag) { nil }
|
9
|
+
before do
|
10
|
+
Cancannible.get_cached_abilities = proc{|user| puts "get_cached_abilities" }
|
11
|
+
end
|
12
|
+
subject { grantee.abilities }
|
13
|
+
it "should call" do
|
14
|
+
subject
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe Cancannible do
|
5
|
+
let(:grantee_class) { User }
|
6
|
+
let(:ability) { :blow }
|
7
|
+
let(:username) { 'username' }
|
8
|
+
|
9
|
+
subject(:grantee) { grantee_class.create!(username: username) }
|
10
|
+
|
11
|
+
describe "#can?" do
|
12
|
+
|
13
|
+
context "with custom attribute association restriction" do
|
14
|
+
let(:resource_class) { Widget }
|
15
|
+
before do
|
16
|
+
Cancannible.setup do |config|
|
17
|
+
config.refine_access category_id: :category_ids
|
18
|
+
end
|
19
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
20
|
+
grantee.can(ability,resource_class)
|
21
|
+
end
|
22
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
23
|
+
subject { grantee.can?(ability,resource) }
|
24
|
+
context "with resource within scope" do
|
25
|
+
let(:category_id) { 3 }
|
26
|
+
it { should be_truthy }
|
27
|
+
end
|
28
|
+
context "with resource in nil scope" do
|
29
|
+
let(:category_id) { nil }
|
30
|
+
it { should be_falsey }
|
31
|
+
end
|
32
|
+
context "with resource not in scope" do
|
33
|
+
let(:category_id) { 2 }
|
34
|
+
it { should be_falsey }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with custom attribute association restriction and allow_nil" do
|
39
|
+
let(:resource_class) { Widget }
|
40
|
+
before do
|
41
|
+
Cancannible.setup do |config|
|
42
|
+
config.refine_access category_id: :category_ids, allow_nil: true
|
43
|
+
end
|
44
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
45
|
+
grantee.can(ability,resource_class)
|
46
|
+
end
|
47
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
48
|
+
subject { grantee.can?(ability,resource) }
|
49
|
+
context "with resource within scope" do
|
50
|
+
let(:category_id) { 3 }
|
51
|
+
it { should be_truthy }
|
52
|
+
end
|
53
|
+
context "with resource in nil scope" do
|
54
|
+
let(:category_id) { nil }
|
55
|
+
it { should be_truthy }
|
56
|
+
end
|
57
|
+
context "with resource not in scope" do
|
58
|
+
let(:category_id) { 2 }
|
59
|
+
it { should be_falsey }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with custom attribute association restriction scoped by ability" do
|
64
|
+
let(:resource_class) { Widget }
|
65
|
+
let(:other_ability) { :suck }
|
66
|
+
before do
|
67
|
+
Cancannible.setup do |config|
|
68
|
+
config.refine_access category_id: :category_ids, scope: ability
|
69
|
+
end
|
70
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
71
|
+
grantee.can(ability,resource_class)
|
72
|
+
grantee.can(other_ability,resource_class)
|
73
|
+
end
|
74
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
75
|
+
context "when scoped ability" do
|
76
|
+
subject { grantee.can?(ability,resource) }
|
77
|
+
context "with resource within scope" do
|
78
|
+
let(:category_id) { 3 }
|
79
|
+
it { should be_truthy }
|
80
|
+
end
|
81
|
+
context "with resource not in scope" do
|
82
|
+
let(:category_id) { 2 }
|
83
|
+
it { should be_falsey }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
context "when other ability" do
|
87
|
+
subject { grantee.can?(other_ability,resource) }
|
88
|
+
context "with resource within scoped ability" do
|
89
|
+
let(:category_id) { 3 }
|
90
|
+
it { should be_truthy }
|
91
|
+
end
|
92
|
+
context "with resource not in scoped ability" do
|
93
|
+
let(:category_id) { 2 }
|
94
|
+
it { should be_truthy }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with custom attribute association restriction scoped by ability exception" do
|
100
|
+
let(:resource_class) { Widget }
|
101
|
+
let(:other_ability) { :suck }
|
102
|
+
before do
|
103
|
+
Cancannible.setup do |config|
|
104
|
+
config.refine_access category_id: :category_ids, except: other_ability
|
105
|
+
end
|
106
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
107
|
+
grantee.can(ability,resource_class)
|
108
|
+
grantee.can(other_ability,resource_class)
|
109
|
+
end
|
110
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
111
|
+
context "when scoped ability" do
|
112
|
+
subject { grantee.can?(ability,resource) }
|
113
|
+
context "with resource within scope" do
|
114
|
+
let(:category_id) { 3 }
|
115
|
+
it { should be_truthy }
|
116
|
+
end
|
117
|
+
context "with resource not in scope" do
|
118
|
+
let(:category_id) { 2 }
|
119
|
+
it { should be_falsey }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
context "when other ability" do
|
123
|
+
subject { grantee.can?(other_ability,resource) }
|
124
|
+
context "with resource within scoped ability" do
|
125
|
+
let(:category_id) { 3 }
|
126
|
+
it { should be_truthy }
|
127
|
+
end
|
128
|
+
context "with resource not in scoped ability" do
|
129
|
+
let(:category_id) { 2 }
|
130
|
+
it { should be_truthy }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with custom attribute association restriction scoped by ability array" do
|
136
|
+
let(:resource_class) { Widget }
|
137
|
+
let(:other_ability) { :suck }
|
138
|
+
before do
|
139
|
+
Cancannible.setup do |config|
|
140
|
+
config.refine_access category_id: :category_ids, scope: [ability,:another_ability]
|
141
|
+
end
|
142
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
143
|
+
grantee.can(ability,resource_class)
|
144
|
+
grantee.can(other_ability,resource_class)
|
145
|
+
end
|
146
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
147
|
+
context "when scoped ability" do
|
148
|
+
subject { grantee.can?(ability,resource) }
|
149
|
+
context "with resource within scope" do
|
150
|
+
let(:category_id) { 3 }
|
151
|
+
it { should be_truthy }
|
152
|
+
end
|
153
|
+
context "with resource not in scope" do
|
154
|
+
let(:category_id) { 2 }
|
155
|
+
it { should be_falsey }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
context "when other ability" do
|
159
|
+
subject { grantee.can?(other_ability,resource) }
|
160
|
+
context "with resource within scoped ability" do
|
161
|
+
let(:category_id) { 3 }
|
162
|
+
it { should be_truthy }
|
163
|
+
end
|
164
|
+
context "with resource not in scoped ability" do
|
165
|
+
let(:category_id) { 2 }
|
166
|
+
it { should be_truthy }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
context "with if-conditional custom attribute association restriction" do
|
171
|
+
let(:resource_class) { Widget }
|
172
|
+
before do
|
173
|
+
Cancannible.setup do |config|
|
174
|
+
config.refine_access category_id: :category_ids, if: proc{ |grantee,model_resource| grantee.username == 'restrict by categories' }
|
175
|
+
end
|
176
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
177
|
+
grantee.can(ability,resource_class)
|
178
|
+
end
|
179
|
+
let!(:resource) { resource_class.create(category_id: category_id) }
|
180
|
+
subject { grantee.can?(ability,resource) }
|
181
|
+
context "with resource not in scope but restriction not applicable" do
|
182
|
+
let(:category_id) { 2 }
|
183
|
+
it { should be_truthy }
|
184
|
+
end
|
185
|
+
context "with resource not in scope and restriction applicable" do
|
186
|
+
let(:username) { 'restrict by categories' }
|
187
|
+
let(:category_id) { 2 }
|
188
|
+
it { should be_falsey }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "with combined attribute association and fixed value restriction" do
|
193
|
+
let(:resource_class) { Widget }
|
194
|
+
before do
|
195
|
+
Cancannible.setup do |config|
|
196
|
+
config.refine_access category_id: :category_ids, name: 'Test'
|
197
|
+
end
|
198
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
199
|
+
grantee.can(ability,resource_class)
|
200
|
+
end
|
201
|
+
let!(:resource) { resource_class.create(category_id: category_id, name: name) }
|
202
|
+
subject { grantee.can?(ability,resource) }
|
203
|
+
context "with resource within scope" do
|
204
|
+
let(:name) { 'Test' }
|
205
|
+
let(:category_id) { 3 }
|
206
|
+
it { should be_truthy }
|
207
|
+
end
|
208
|
+
context "with resource not in scope (excluded by attribute association)" do
|
209
|
+
let(:name) { 'Test' }
|
210
|
+
let(:category_id) { 2 }
|
211
|
+
it { should be_falsey }
|
212
|
+
end
|
213
|
+
context "with resource not in scope (excluded by fixed value)" do
|
214
|
+
let(:name) { 'Not Test' }
|
215
|
+
let(:category_id) { 3 }
|
216
|
+
it { should be_falsey }
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe Cancannible do
|
5
|
+
let(:grantee_class) { User }
|
6
|
+
|
7
|
+
context "with mulitple sources of permissions inheritance" do
|
8
|
+
|
9
|
+
describe "##inheritable_permissions" do
|
10
|
+
subject { grantee_class.inheritable_permissions }
|
11
|
+
it { should eql([:roles, :group]) }
|
12
|
+
end
|
13
|
+
|
14
|
+
let!(:role_a) { Role.create! }
|
15
|
+
let!(:role_b) { Role.create! }
|
16
|
+
let!(:group_a) { Group.create! }
|
17
|
+
let!(:group_b) { Group.create! }
|
18
|
+
subject(:grantee) {
|
19
|
+
u = grantee_class.new(group: group_a)
|
20
|
+
u.roles << role_a
|
21
|
+
u.save!
|
22
|
+
u
|
23
|
+
}
|
24
|
+
|
25
|
+
context "with a symbolic resource" do
|
26
|
+
let!(:resource) { :something }
|
27
|
+
|
28
|
+
describe "#can?" do
|
29
|
+
subject { grantee.can?(:read, resource) }
|
30
|
+
context "when no permission" do
|
31
|
+
it { should be_falsey }
|
32
|
+
end
|
33
|
+
context "when permission assigned to grantee themselves" do
|
34
|
+
before { grantee.can(:read, resource) }
|
35
|
+
it { should be_truthy }
|
36
|
+
end
|
37
|
+
context "when permission inherited thru belongs_to association" do
|
38
|
+
before { group_a.can(:read, resource) }
|
39
|
+
it { should be_truthy }
|
40
|
+
end
|
41
|
+
context "when permission inherited thru habtm association" do
|
42
|
+
before { role_a.can(:read, resource) }
|
43
|
+
it { should be_truthy }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
context "with a resource class" do
|
51
|
+
let!(:resource) { Widget }
|
52
|
+
|
53
|
+
describe "#can?" do
|
54
|
+
subject { grantee.can?(:read, resource) }
|
55
|
+
context "when permission is not set" do
|
56
|
+
it { should be_falsey }
|
57
|
+
end
|
58
|
+
context "when permission assigned to grantee themselves" do
|
59
|
+
before { grantee.can(:read, resource) }
|
60
|
+
it { should be_truthy }
|
61
|
+
end
|
62
|
+
context "when permission inherited thru belongs_to association" do
|
63
|
+
before { group_a.can(:read, resource) }
|
64
|
+
it { should be_truthy }
|
65
|
+
end
|
66
|
+
context "when permission inherited thru habtm association" do
|
67
|
+
before { role_a.can(:read, resource) }
|
68
|
+
it { should be_truthy }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with a resource instance" do
|
75
|
+
let!(:resource) { Widget.create! }
|
76
|
+
let!(:other_resource) { Widget.create! }
|
77
|
+
|
78
|
+
describe "#can?" do
|
79
|
+
subject { grantee.can?(:read, resource) }
|
80
|
+
context "when permission is not set" do
|
81
|
+
it { should be_falsey }
|
82
|
+
end
|
83
|
+
context "when permission assigned to grantee themselves" do
|
84
|
+
before { grantee.can(:read, resource) }
|
85
|
+
it { should be_truthy }
|
86
|
+
context "but for other instances" do
|
87
|
+
subject { grantee.can?(:read, other_resource) }
|
88
|
+
it { should be_falsey }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
context "when permission inherited thru belongs_to association" do
|
92
|
+
before { group_a.can(:read, resource) }
|
93
|
+
it { should be_truthy }
|
94
|
+
context "but for other instances" do
|
95
|
+
subject { grantee.can?(:read, other_resource) }
|
96
|
+
it { should be_falsey }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
context "when permission inherited thru habtm association" do
|
100
|
+
before { role_a.can(:read, resource) }
|
101
|
+
it { should be_truthy }
|
102
|
+
context "but for other instances" do
|
103
|
+
subject { grantee.can?(:read, other_resource) }
|
104
|
+
it { should be_falsey }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#cannot?" do
|
110
|
+
subject { grantee.cannot?(:read, resource) }
|
111
|
+
context "when permission is not asserted for the grantee themselves" do
|
112
|
+
it { should be_truthy }
|
113
|
+
end
|
114
|
+
context "when permission is not asserted but can is for the grantee themselves" do
|
115
|
+
before { grantee.can(:read, resource) }
|
116
|
+
it { should be_falsey }
|
117
|
+
end
|
118
|
+
context "when permission is not asserted but can is thru belongs_to association" do
|
119
|
+
before { group_a.can(:read, resource) }
|
120
|
+
it { should be_falsey }
|
121
|
+
end
|
122
|
+
context "when permission is not asserted but can is thru habtm association" do
|
123
|
+
before { role_a.can(:read, resource) }
|
124
|
+
it { should be_falsey }
|
125
|
+
end
|
126
|
+
context "when permission is asserted for the grantee themselves" do
|
127
|
+
before { grantee.cannot(:read, resource) }
|
128
|
+
it { should be_truthy }
|
129
|
+
end
|
130
|
+
context "when permission is asserted thru belongs_to association" do
|
131
|
+
before { group_a.cannot(:read, resource) }
|
132
|
+
it { should be_truthy }
|
133
|
+
end
|
134
|
+
context "when permission is asserted thru habtm association" do
|
135
|
+
before { role_a.cannot(:read, resource) }
|
136
|
+
it { should be_truthy }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|