cancannible 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +45 -1
- data/lib/cancannible.rb +3 -2
- data/lib/cancannible/config.rb +3 -1
- data/lib/cancannible/grantee.rb +125 -0
- data/lib/cancannible/{ability_preload_adapter.rb → preload_adapter.rb} +2 -2
- data/lib/cancannible/preloader.rb +115 -0
- data/lib/cancannible/version.rb +1 -1
- data/lib/generators/cancannible/templates/cancannible_initializer.rb +6 -0
- data/spec/support/models.rb +6 -6
- data/spec/unit/custom_refinements_spec.rb +28 -0
- data/spec/unit/{base_spec.rb → grantee_spec.rb} +1 -1
- metadata +7 -6
- data/lib/cancannible/base.rb +0 -213
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YWE0OWZkMjhlMWQ3ODMzNTVmZWNiMTk2ZWM5YmU2YjMyY2I1NTAwMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YjQ0NDZhNzQ5MzdhMDJiMDQ4NzRjMjI1MDE5ZGY1N2ViZjU2NThlMA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Zjc2OGYzMGI1ODA5ZmY4ZmQ3ZGRhNTU5MDM2MmRiOWJmNDNmNzI4MzZjYTY0
|
10
|
+
ZDNiNDM3ODVmZTExYzM5NDQ1ZmQyNDZmZjc0NDFiYWIxZTM2YjcwMTdiMGU0
|
11
|
+
YTliZGI0YjFhNDdjY2RmZWNhZjNhY2YyNDU5YTQ3MDQ3NDRhNjY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MjFhMzBmNzQ5YTcyMzBmOTViMTYwMjEwNDI2YjllNjUxZjEzYjkxNmZjOTVi
|
14
|
+
YmVkOWZjMWUyODBiMDQ3MmY4YWExMDJiNmUzZTkzZTBlY2MwZWFjMzFmOWM0
|
15
|
+
MjNiMTBmMGFjNGI3OTllNTkxMjM3NTBmZDViOWYwNmVlZGMwZWE=
|
data/README.md
CHANGED
@@ -34,6 +34,7 @@ Or install it yourself as:
|
|
34
34
|
|
35
35
|
$ gem install cancannible
|
36
36
|
|
37
|
+
|
37
38
|
## Configuration
|
38
39
|
|
39
40
|
A generator is provided to create:
|
@@ -44,13 +45,56 @@ After installing the gem, run the generator:
|
|
44
45
|
|
45
46
|
$ rails generate cancannible:install
|
46
47
|
|
48
|
+
|
49
|
+
## Enable Cancannible support in a model
|
50
|
+
|
51
|
+
Include Cancannible::Grantee in each model that it will be valid to assign permissions to.
|
52
|
+
|
53
|
+
For example, if we have a User model associated with a Group, and both can have permissions assigned:
|
54
|
+
|
55
|
+
class User < ActiveRecord::Base
|
56
|
+
belongs_to :group
|
57
|
+
include Cancannible::Grantee
|
58
|
+
end
|
59
|
+
|
60
|
+
class Group < ActiveRecord::Base
|
61
|
+
has_many :users
|
62
|
+
include Cancannible::Grantee
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
## Enabling Permissions inheritance
|
67
|
+
|
68
|
+
By default, permissions are not inherited from association.
|
69
|
+
User the `inherit_permissions_from` class method to declare how permissions can be inherited.
|
70
|
+
|
71
|
+
For example:
|
72
|
+
|
73
|
+
class User < ActiveRecord::Base
|
74
|
+
belongs_to :group
|
75
|
+
include Cancannible::Grantee
|
76
|
+
inherit_permissions_from :group
|
77
|
+
end
|
78
|
+
|
79
|
+
Or:
|
80
|
+
|
81
|
+
class User < ActiveRecord::Base
|
82
|
+
belongs_to :group
|
83
|
+
has_many :roles_users, class_name: 'RolesUsers'
|
84
|
+
has_many :roles, through: :roles_users
|
85
|
+
include Cancannible::Grantee
|
86
|
+
inherit_permissions_from :group, :roles
|
87
|
+
end
|
88
|
+
|
89
|
+
|
47
90
|
## The Cancannible initialization file
|
48
91
|
|
49
92
|
See the initialization file template for specific instructions. Use the initialization file to configure:
|
50
93
|
* abilities caching
|
51
94
|
* general-purpose access refinements
|
52
95
|
|
53
|
-
|
96
|
+
|
97
|
+
### Configuring cached abilities storage
|
54
98
|
|
55
99
|
Cancannible does not implement any specific storage mechanism - that is up to you to provide if you wish.
|
56
100
|
|
data/lib/cancannible.rb
CHANGED
data/lib/cancannible/config.rb
CHANGED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Cancannible::Grantee
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_attribute :inheritable_permissions
|
6
|
+
self.inheritable_permissions = [] # default
|
7
|
+
|
8
|
+
has_many :permissions, as: :permissible, dependent: :destroy do
|
9
|
+
# generally use instance.can method, not permissions<< directly
|
10
|
+
def <<(arg)
|
11
|
+
ability, resource = arg
|
12
|
+
resource = nil if resource.blank?
|
13
|
+
asserted = arg[2].nil? ? true : arg[2]
|
14
|
+
|
15
|
+
case resource
|
16
|
+
when Class, Symbol
|
17
|
+
resource_type = resource.to_s
|
18
|
+
resource_id = nil
|
19
|
+
when nil
|
20
|
+
resource_type = resource_id = nil
|
21
|
+
else
|
22
|
+
resource_type = resource.class.to_s
|
23
|
+
resource_id = resource.try(:id)
|
24
|
+
end
|
25
|
+
|
26
|
+
permission = find_by_asserted_and_ability_and_resource_id_and_resource_type(
|
27
|
+
asserted, ability, resource_id, resource_type)
|
28
|
+
unless permission
|
29
|
+
permission = find_or_initialize_by_asserted_and_ability_and_resource_id_and_resource_type(
|
30
|
+
!asserted, ability, resource_id, resource_type)
|
31
|
+
permission.asserted = asserted
|
32
|
+
permission.save!
|
33
|
+
end
|
34
|
+
|
35
|
+
# if Rails.version =~ /3\.0/ # the rails 3.0 way
|
36
|
+
# proxy_owner.instance_variable_set :@permissions, nil # invalidate the owner's permissions collection
|
37
|
+
# proxy_owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
|
38
|
+
# else
|
39
|
+
proxy_association.owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
|
40
|
+
# end
|
41
|
+
permission
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
|
49
|
+
# Command: configures the set of associations (array of symbols) from which permissions should be inherited
|
50
|
+
def inherit_permissions_from(*relations)
|
51
|
+
self.inheritable_permissions = relations
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the Ability set for the owner.
|
57
|
+
# Set +refresh+ to true to force a reload of permissions.
|
58
|
+
def abilities(refresh = false)
|
59
|
+
@abilities = if refresh
|
60
|
+
nil
|
61
|
+
elsif Cancannible.get_cached_abilities.respond_to?(:call)
|
62
|
+
Cancannible.get_cached_abilities.call(self)
|
63
|
+
end
|
64
|
+
return @abilities if @abilities
|
65
|
+
|
66
|
+
@abilities ||= if ability_class = ('Ability'.constantize rescue nil)
|
67
|
+
unless ability_class.included_modules.include?(Cancannible::PreloadAdapter)
|
68
|
+
ability_class.send :include, Cancannible::PreloadAdapter
|
69
|
+
end
|
70
|
+
ability_class.new(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
Cancannible.store_cached_abilities.call(self,@abilities) if Cancannible.store_cached_abilities.respond_to?(:call)
|
74
|
+
@abilities
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the collection of inherited permission records
|
78
|
+
def inherited_permissions
|
79
|
+
inherited_perms = []
|
80
|
+
self.class.inheritable_permissions.each do |relation|
|
81
|
+
Array(self.send(relation)).each do |record|
|
82
|
+
inherited_perms.concat(record.permissions.reload)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
inherited_perms
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns true it the +ability+ is permitted on +resource+ - persisted or dynamic (delegated to CanCan)
|
89
|
+
def can?(ability, resource)
|
90
|
+
abilities.can?(ability, resource)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns true it the +ability+ is prohibited on +resource+ - persisted or dynamic (delegated to CanCan)
|
94
|
+
def cannot?(ability, resource)
|
95
|
+
abilities.cannot?(ability, resource)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Command: grant the permission to do +ability+ on +resource+
|
99
|
+
def can(ability, resource)
|
100
|
+
permissions << [ability, resource]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Command: prohibit the permission to do +ability+ on +resource+
|
104
|
+
def cannot(ability, resource)
|
105
|
+
permissions << [ability, resource, false]
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
module Cancannible
|
112
|
+
# This module is automatically included into all controllers.
|
113
|
+
# It overrides some CanCan ControllerAdditions
|
114
|
+
module ControllerAdditions
|
115
|
+
def current_ability
|
116
|
+
current_user.try(:abilities)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if defined? ActionController::Base
|
122
|
+
ActionController::Base.class_eval do
|
123
|
+
include Cancannible::ControllerAdditions
|
124
|
+
end
|
125
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Cancannible::
|
1
|
+
module Cancannible::PreloadAdapter
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
@@ -6,7 +6,7 @@ module Cancannible::AbilityPreloadAdapter
|
|
6
6
|
# Tap Ability.new to first preload permissions via Cancannible
|
7
7
|
alias_method :cancan_initialize, :initialize
|
8
8
|
def initialize(user)
|
9
|
-
|
9
|
+
Cancannible::Preloader.preload_abilities!(user,self)
|
10
10
|
cancan_initialize(user)
|
11
11
|
end
|
12
12
|
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class Cancannible::Preloader
|
2
|
+
|
3
|
+
def self.preload_abilities!(grantee,cancan_ability_object)
|
4
|
+
new(grantee,cancan_ability_object).preload!
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_accessor :grantee
|
8
|
+
attr_accessor :cancan_ability_object
|
9
|
+
|
10
|
+
def initialize(grantee,cancan_ability_object)
|
11
|
+
self.grantee = grantee
|
12
|
+
self.cancan_ability_object = cancan_ability_object
|
13
|
+
end
|
14
|
+
|
15
|
+
def preload!
|
16
|
+
return unless grantee.respond_to?(:inherited_permissions)
|
17
|
+
# load inherited permissions to CanCan Abilities
|
18
|
+
preload_abilities_from_permissions(grantee.inherited_permissions)
|
19
|
+
# load user-based permissions from database to CanCan Abilities
|
20
|
+
preload_abilities_from_permissions(grantee.permissions.reload)
|
21
|
+
# return the ability object
|
22
|
+
cancan_ability_object
|
23
|
+
end
|
24
|
+
|
25
|
+
def preload_abilities_from_permissions(perms)
|
26
|
+
perms.each do |permission|
|
27
|
+
ability = permission.ability.to_sym
|
28
|
+
action = permission.asserted ? :can : :cannot
|
29
|
+
|
30
|
+
resource_type,model_resource = resolve_resource_type(permission.resource_type)
|
31
|
+
|
32
|
+
if !resource_type || resource_type.is_a?(Symbol)
|
33
|
+
# nil or symbolic resource types: apply generic unrestricted permission to the resource_type
|
34
|
+
cancan_ability_object.send( action, ability, resource_type )
|
35
|
+
next
|
36
|
+
else
|
37
|
+
# model-based resource types: skip if we cannot get a model instance
|
38
|
+
next unless model_resource
|
39
|
+
end
|
40
|
+
|
41
|
+
if permission.resource_id.nil?
|
42
|
+
|
43
|
+
if action == :cannot
|
44
|
+
# apply generic unrestricted permission to the class
|
45
|
+
cancan_ability_object.send( action, ability, resource_type )
|
46
|
+
else
|
47
|
+
|
48
|
+
refinements = resolve_resource_refinements(ability,model_resource)
|
49
|
+
|
50
|
+
if refinements.empty?
|
51
|
+
# apply generic unrestricted permission to the class
|
52
|
+
cancan_ability_object.send( action, ability, resource_type )
|
53
|
+
else
|
54
|
+
secondary_refinements = resolve_resource_refinements(ability,model_resource,2).presence || [{}]
|
55
|
+
refinements.each do |refinement|
|
56
|
+
secondary_refinements.each do |secondary_refinement|
|
57
|
+
cancan_ability_object.send( action, ability, resource_type, refinement.merge(secondary_refinement))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
elsif resource_type.find_by_id(permission.resource_id)
|
65
|
+
cancan_ability_object.send( action, ability, resource_type, id: permission.resource_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def resolve_resource_type(given_resource_type)
|
72
|
+
model_resource = nil
|
73
|
+
resource_type = given_resource_type
|
74
|
+
resource_type = resource_type==resource_type.downcase ? resource_type.to_sym : resource_type.constantize rescue nil
|
75
|
+
model_resource = resource_type.respond_to?(:new) ? resource_type.new : resource_type rescue nil
|
76
|
+
[resource_type,model_resource]
|
77
|
+
end
|
78
|
+
|
79
|
+
def resolve_resource_refinements(ability,model_resource,stage=1)
|
80
|
+
Array(Cancannible.refinements[stage-1]).each_with_object([]) do |refinement,memo|
|
81
|
+
refinement_attributes = refinement.dup
|
82
|
+
|
83
|
+
allow_nil = !!(refinement_attributes.delete(:allow_nil))
|
84
|
+
|
85
|
+
refinement_if_condition = refinement_attributes.delete(:if)
|
86
|
+
next if refinement_if_condition.respond_to?(:call) && !refinement_if_condition.call(grantee,model_resource)
|
87
|
+
|
88
|
+
refinement_scope = Array(refinement_attributes.delete(:scope))
|
89
|
+
next if refinement_scope.present? && !refinement_scope.include?(ability)
|
90
|
+
|
91
|
+
refinement_except = Array(refinement_attributes.delete(:except))
|
92
|
+
next if refinement_except.present? && refinement_except.include?(ability)
|
93
|
+
|
94
|
+
refinement_attribute_names = refinement_attributes.keys.map{|k| "#{k}" }
|
95
|
+
next unless (refinement_attribute_names - model_resource.attribute_names).empty?
|
96
|
+
|
97
|
+
restriction = {}
|
98
|
+
refinement_attributes.each do |key,value|
|
99
|
+
if value.is_a?(Symbol)
|
100
|
+
if grantee.respond_to?(value)
|
101
|
+
restriction[key] = if allow_nil
|
102
|
+
Array(grantee.send(value)) + [nil]
|
103
|
+
else
|
104
|
+
grantee.send(value)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
restriction[key] = value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
memo.push(restriction) if restriction.present?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/lib/cancannible/version.rb
CHANGED
@@ -94,4 +94,10 @@ Cancannible.setup do |config|
|
|
94
94
|
# and `model_resource` is an example record (unsaved) of the kind of resource that the rule is being applied to.
|
95
95
|
|
96
96
|
|
97
|
+
# Multi-stage refinement syntax example:
|
98
|
+
# config.refine_access customer_id: :accessible_customer_ids, stage: 2
|
99
|
+
#
|
100
|
+
# By default, access refinements are "stage 1" i.e. applied directly to the permissions being loaded.
|
101
|
+
# By specifying stage 2, this refinement is applied on top of all stage 1 refinements (if possible / applicable)
|
102
|
+
|
97
103
|
end
|
data/spec/support/models.rb
CHANGED
@@ -11,15 +11,15 @@ class Permission < ActiveRecord::Base
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class Member < ActiveRecord::Base
|
14
|
-
include Cancannible
|
14
|
+
include Cancannible::Grantee
|
15
15
|
end
|
16
16
|
|
17
17
|
class User < ActiveRecord::Base
|
18
18
|
has_many :roles_users, class_name: 'RolesUsers'
|
19
|
-
has_many :roles, :
|
19
|
+
has_many :roles, through: :roles_users
|
20
20
|
belongs_to :group
|
21
21
|
|
22
|
-
include Cancannible
|
22
|
+
include Cancannible::Grantee
|
23
23
|
inherit_permissions_from :roles, :group
|
24
24
|
end
|
25
25
|
|
@@ -30,15 +30,15 @@ end
|
|
30
30
|
|
31
31
|
class Role < ActiveRecord::Base
|
32
32
|
has_many :roles_users, :class_name => 'RolesUsers'
|
33
|
-
has_many :users, :
|
33
|
+
has_many :users, through: :roles_users
|
34
34
|
|
35
|
-
include Cancannible
|
35
|
+
include Cancannible::Grantee
|
36
36
|
end
|
37
37
|
|
38
38
|
class Group < ActiveRecord::Base
|
39
39
|
has_many :users
|
40
40
|
|
41
|
-
include Cancannible
|
41
|
+
include Cancannible::Grantee
|
42
42
|
end
|
43
43
|
|
44
44
|
class Widget < ActiveRecord::Base
|
@@ -217,6 +217,34 @@ describe Cancannible do
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
220
|
+
context "with stage 2 restriction" do
|
221
|
+
let(:resource_class) { Widget }
|
222
|
+
before do
|
223
|
+
Cancannible.setup do |config|
|
224
|
+
config.refine_access category_id: :category_ids
|
225
|
+
config.refine_access name: 'Test', stage: 2
|
226
|
+
end
|
227
|
+
allow(grantee).to receive(:category_ids).and_return([1,3])
|
228
|
+
grantee.can(ability,resource_class)
|
229
|
+
end
|
230
|
+
let!(:resource) { resource_class.create(category_id: category_id, name: name) }
|
231
|
+
subject { grantee.can?(ability,resource) }
|
232
|
+
context "with resource within scope" do
|
233
|
+
let(:name) { 'Test' }
|
234
|
+
let(:category_id) { 3 }
|
235
|
+
it { should be_truthy }
|
236
|
+
end
|
237
|
+
context "with resource not in scope (excluded by attribute association)" do
|
238
|
+
let(:name) { 'Test' }
|
239
|
+
let(:category_id) { 2 }
|
240
|
+
it { should be_falsey }
|
241
|
+
end
|
242
|
+
context "with resource not in scope (excluded by stage 2 refinement)" do
|
243
|
+
let(:name) { 'Not Test' }
|
244
|
+
let(:category_id) { 3 }
|
245
|
+
it { should be_falsey }
|
246
|
+
end
|
247
|
+
end
|
220
248
|
|
221
249
|
end
|
222
250
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cancannible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Gallagher
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -154,9 +154,10 @@ files:
|
|
154
154
|
- Rakefile
|
155
155
|
- cancannible.gemspec
|
156
156
|
- lib/cancannible.rb
|
157
|
-
- lib/cancannible/ability_preload_adapter.rb
|
158
|
-
- lib/cancannible/base.rb
|
159
157
|
- lib/cancannible/config.rb
|
158
|
+
- lib/cancannible/grantee.rb
|
159
|
+
- lib/cancannible/preload_adapter.rb
|
160
|
+
- lib/cancannible/preloader.rb
|
160
161
|
- lib/cancannible/version.rb
|
161
162
|
- lib/generators/cancannible/install_generator.rb
|
162
163
|
- lib/generators/cancannible/templates/cancannible_initializer.rb
|
@@ -166,10 +167,10 @@ files:
|
|
166
167
|
- spec/support/ability.rb
|
167
168
|
- spec/support/migrations_helper.rb
|
168
169
|
- spec/support/models.rb
|
169
|
-
- spec/unit/base_spec.rb
|
170
170
|
- spec/unit/cached_abilities_spec.rb
|
171
171
|
- spec/unit/config_spec.rb
|
172
172
|
- spec/unit/custom_refinements_spec.rb
|
173
|
+
- spec/unit/grantee_spec.rb
|
173
174
|
- spec/unit/inherited_permissions_spec.rb
|
174
175
|
homepage: https://github.com/evendis/cancannible
|
175
176
|
licenses:
|
@@ -200,8 +201,8 @@ test_files:
|
|
200
201
|
- spec/support/ability.rb
|
201
202
|
- spec/support/migrations_helper.rb
|
202
203
|
- spec/support/models.rb
|
203
|
-
- spec/unit/base_spec.rb
|
204
204
|
- spec/unit/cached_abilities_spec.rb
|
205
205
|
- spec/unit/config_spec.rb
|
206
206
|
- spec/unit/custom_refinements_spec.rb
|
207
|
+
- spec/unit/grantee_spec.rb
|
207
208
|
- spec/unit/inherited_permissions_spec.rb
|
data/lib/cancannible/base.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
module Cancannible
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
included do
|
5
|
-
class_attribute :inheritable_permissions
|
6
|
-
self.inheritable_permissions = [] # default
|
7
|
-
|
8
|
-
has_many :permissions, as: :permissible, dependent: :destroy do
|
9
|
-
# generally use instance.can method, not permissions<< directly
|
10
|
-
def <<(arg)
|
11
|
-
ability, resource = arg
|
12
|
-
resource = nil if resource.blank?
|
13
|
-
asserted = arg[2].nil? ? true : arg[2]
|
14
|
-
|
15
|
-
case resource
|
16
|
-
when Class, Symbol
|
17
|
-
resource_type = resource.to_s
|
18
|
-
resource_id = nil
|
19
|
-
when nil
|
20
|
-
resource_type = resource_id = nil
|
21
|
-
else
|
22
|
-
resource_type = resource.class.to_s
|
23
|
-
resource_id = resource.try(:id)
|
24
|
-
end
|
25
|
-
|
26
|
-
permission = find_by_asserted_and_ability_and_resource_id_and_resource_type(
|
27
|
-
asserted, ability, resource_id, resource_type)
|
28
|
-
unless permission
|
29
|
-
permission = find_or_initialize_by_asserted_and_ability_and_resource_id_and_resource_type(
|
30
|
-
!asserted, ability, resource_id, resource_type)
|
31
|
-
permission.asserted = asserted
|
32
|
-
permission.save!
|
33
|
-
end
|
34
|
-
|
35
|
-
# if Rails.version =~ /3\.0/ # the rails 3.0 way
|
36
|
-
# proxy_owner.instance_variable_set :@permissions, nil # invalidate the owner's permissions collection
|
37
|
-
# proxy_owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
|
38
|
-
# else
|
39
|
-
proxy_association.owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
|
40
|
-
# end
|
41
|
-
permission
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
module ClassMethods
|
48
|
-
def inherit_permissions_from(*relations)
|
49
|
-
self.inheritable_permissions = relations
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Returns the Ability set for the owner.
|
54
|
-
# Set +refresh+ to true to force a reload of permissions.
|
55
|
-
def abilities(refresh = false)
|
56
|
-
@abilities = if refresh
|
57
|
-
nil
|
58
|
-
elsif get_cached_abilities.respond_to?(:call)
|
59
|
-
get_cached_abilities.call(self)
|
60
|
-
end
|
61
|
-
return @abilities if @abilities
|
62
|
-
|
63
|
-
@abilities ||= if ability_class = ('Ability'.constantize rescue nil)
|
64
|
-
unless ability_class.included_modules.include?(Cancannible::AbilityPreloadAdapter)
|
65
|
-
ability_class.send :include, Cancannible::AbilityPreloadAdapter
|
66
|
-
end
|
67
|
-
ability_class.new(self)
|
68
|
-
end
|
69
|
-
|
70
|
-
store_cached_abilities.call(self,@abilities) if store_cached_abilities.respond_to?(:call)
|
71
|
-
@abilities
|
72
|
-
end
|
73
|
-
|
74
|
-
def preload_abilities(cancan_ability_object)
|
75
|
-
# load inherited permissions to CanCan Abilities
|
76
|
-
preload_abilities_from_permissions(cancan_ability_object, inherited_permissions)
|
77
|
-
# load user-based permissions from database to CanCan Abilities
|
78
|
-
preload_abilities_from_permissions(cancan_ability_object, self.permissions.reload)
|
79
|
-
cancan_ability_object
|
80
|
-
end
|
81
|
-
|
82
|
-
def inherited_permissions
|
83
|
-
inherited_perms = []
|
84
|
-
self.class.inheritable_permissions.each do |relation|
|
85
|
-
Array(self.send(relation)).each do |record|
|
86
|
-
inherited_perms.concat(record.permissions.reload)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
inherited_perms
|
90
|
-
end
|
91
|
-
|
92
|
-
# test for a permission - persisted or dynamic (delegated to CanCan)
|
93
|
-
def can?(ability, resource)
|
94
|
-
abilities.can?(ability, resource)
|
95
|
-
end
|
96
|
-
|
97
|
-
# test for a prohibition - persisted or dynamic (delegated to CanCan)
|
98
|
-
def cannot?(ability, resource)
|
99
|
-
abilities.cannot?(ability, resource)
|
100
|
-
end
|
101
|
-
|
102
|
-
# define a persisted permission
|
103
|
-
def can(ability, resource)
|
104
|
-
permissions << [ability, resource]
|
105
|
-
end
|
106
|
-
|
107
|
-
# define a persisted prohibition
|
108
|
-
def cannot(ability, resource)
|
109
|
-
permissions << [ability, resource, false]
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def preload_abilities_from_permissions(cancan_ability_object,perms)
|
115
|
-
perms.each do |permission|
|
116
|
-
ability = permission.ability.to_sym
|
117
|
-
action = permission.asserted ? :can : :cannot
|
118
|
-
|
119
|
-
if resource_type = permission.resource_type
|
120
|
-
begin
|
121
|
-
resource_type = resource_type==resource_type.downcase ? resource_type.to_sym : resource_type.constantize
|
122
|
-
model_resource = resource_type.respond_to?(:new) ? resource_type.new : resource_type
|
123
|
-
rescue
|
124
|
-
model_resource = nil
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
if !resource_type || resource_type.is_a?(Symbol)
|
129
|
-
# nil or symbolic resource types:
|
130
|
-
# apply generic unrestricted permission to the resource_type
|
131
|
-
cancan_ability_object.send( action, ability, resource_type )
|
132
|
-
next
|
133
|
-
else
|
134
|
-
# model-based resource types:
|
135
|
-
# skip if we cannot get a model instance
|
136
|
-
next unless model_resource
|
137
|
-
end
|
138
|
-
|
139
|
-
if permission.resource_id.nil?
|
140
|
-
|
141
|
-
if action == :cannot
|
142
|
-
# apply generic unrestricted permission to the class
|
143
|
-
cancan_ability_object.send( action, ability, resource_type )
|
144
|
-
else
|
145
|
-
|
146
|
-
refinements = Cancannible.refinements.each_with_object([]) do |refinement,memo|
|
147
|
-
refinement_attributes = refinement.dup
|
148
|
-
|
149
|
-
allow_nil = !!(refinement_attributes.delete(:allow_nil))
|
150
|
-
|
151
|
-
refinement_if_condition = refinement_attributes.delete(:if)
|
152
|
-
next if refinement_if_condition.respond_to?(:call) && !refinement_if_condition.call(self,model_resource)
|
153
|
-
|
154
|
-
refinement_scope = Array(refinement_attributes.delete(:scope))
|
155
|
-
next if refinement_scope.present? && !refinement_scope.include?(ability)
|
156
|
-
|
157
|
-
refinement_except = Array(refinement_attributes.delete(:except))
|
158
|
-
next if refinement_except.present? && refinement_except.include?(ability)
|
159
|
-
|
160
|
-
refinement_attribute_names = refinement_attributes.keys.map{|k| "#{k}" }
|
161
|
-
next unless (refinement_attribute_names - model_resource.attribute_names).empty?
|
162
|
-
|
163
|
-
restriction = {}
|
164
|
-
refinement_attributes.each do |key,value|
|
165
|
-
if value.is_a?(Symbol)
|
166
|
-
if self.respond_to?(value)
|
167
|
-
restriction[key] = if allow_nil
|
168
|
-
Array(self.send(value)) + [nil]
|
169
|
-
else
|
170
|
-
self.send(value)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
else
|
174
|
-
restriction[key] = value
|
175
|
-
end
|
176
|
-
end
|
177
|
-
memo.push(restriction) if restriction.present?
|
178
|
-
end
|
179
|
-
|
180
|
-
if refinements.empty?
|
181
|
-
# apply generic unrestricted permission to the class
|
182
|
-
cancan_ability_object.send( action, ability, resource_type )
|
183
|
-
else
|
184
|
-
refinements.each do |refinement|
|
185
|
-
cancan_ability_object.send( action, ability, resource_type, refinement)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
end
|
190
|
-
|
191
|
-
elsif resource_type.find_by_id(permission.resource_id)
|
192
|
-
cancan_ability_object.send( action, ability, resource_type, id: permission.resource_id)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
|
199
|
-
module Cancannible
|
200
|
-
# This module is automatically included into all controllers.
|
201
|
-
# It overrides some CanCan ControllerAdditions
|
202
|
-
module ControllerAdditions
|
203
|
-
def current_ability
|
204
|
-
current_user.try(:abilities)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
if defined? ActionController::Base
|
210
|
-
ActionController::Base.class_eval do
|
211
|
-
include Cancannible::ControllerAdditions
|
212
|
-
end
|
213
|
-
end
|