cancannible 0.0.1 → 0.0.2
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 +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
|