accessly 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 +7 -0
- data/.editorconfig +16 -0
- data/.gitignore +54 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +128 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/accessly.gemspec +32 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/accessly.rb +23 -0
- data/lib/accessly/base.rb +33 -0
- data/lib/accessly/models/permitted_action.rb +7 -0
- data/lib/accessly/models/permitted_action_on_object.rb +8 -0
- data/lib/accessly/permission/grant.rb +95 -0
- data/lib/accessly/permission/revoke.rb +87 -0
- data/lib/accessly/permitted_actions/base.rb +40 -0
- data/lib/accessly/permitted_actions/on_object_query.rb +65 -0
- data/lib/accessly/permitted_actions/query.rb +42 -0
- data/lib/accessly/policy/base.rb +269 -0
- data/lib/accessly/query.rb +122 -0
- data/lib/accessly/query_builder.rb +24 -0
- data/lib/accessly/version.rb +3 -0
- data/lib/generators/accessly/install/USAGE +5 -0
- data/lib/generators/accessly/install/install_generator.rb +51 -0
- data/lib/generators/accessly/install/templates/db/migrate/create_permitted_action_on_objects.rb +15 -0
- data/lib/generators/accessly/install/templates/db/migrate/create_permitted_actions.rb +13 -0
- metadata +176 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Accessly
|
|
2
|
+
module Permission
|
|
3
|
+
class Grant < Accessly::Base
|
|
4
|
+
|
|
5
|
+
# Create an instance of Accessly::Permission::Grant
|
|
6
|
+
# Pass in an ActiveRecord::Base for actor
|
|
7
|
+
#
|
|
8
|
+
# @param actor [ActiveRecord::Base] The actor to grant permission
|
|
9
|
+
def initialize(actor)
|
|
10
|
+
super(actor)
|
|
11
|
+
@actor = case actor
|
|
12
|
+
when ActiveRecord::Base
|
|
13
|
+
actor
|
|
14
|
+
else
|
|
15
|
+
raise Accessly::GrantError.new("Actor is not an ActiveRecord::Base object")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Grant a permission to an actor.
|
|
20
|
+
# @return [nil]
|
|
21
|
+
# @overload grant!(action_id, object_type)
|
|
22
|
+
# Allow permission on a general action in the given namespace represented by object_type.
|
|
23
|
+
# A grant is universally unique and is enforced at the database level.
|
|
24
|
+
#
|
|
25
|
+
# @param action_id [Integer] The action to grant for the object
|
|
26
|
+
# @param object_type [String] The namespace of the given action_id.
|
|
27
|
+
# @raise [Accessly::GrantError] if the operation does not succeed
|
|
28
|
+
# @return [nil] Returns nil if successful
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# # Allow the user access to posts for action id 3
|
|
32
|
+
# Accessly::Permission::Grant.new(user).grant!(3, "posts")
|
|
33
|
+
# @example
|
|
34
|
+
# # Allow the user access to posts for action id 3 on a segment
|
|
35
|
+
# Accessly::Permission::Grant.new(user).on_segment(1).grant!(3, "posts")
|
|
36
|
+
#
|
|
37
|
+
# @overload grant!(action_id, object_type, object_id)
|
|
38
|
+
# Allow permission on an ActiveRecord object.
|
|
39
|
+
# A grant is universally unique and is enforced at the database level.
|
|
40
|
+
#
|
|
41
|
+
# @param action_id [Integer] The action to grant for the object
|
|
42
|
+
# @param object_type [ActiveRecord::Base] The ActiveRecord model that receives a permission grant.
|
|
43
|
+
# @param object_id [Integer] The id of the ActiveRecord object which receives a permission grant
|
|
44
|
+
# @raise [Accessly::GrantError] if the operation does not succeed
|
|
45
|
+
# @return [nil] Returns nil if successful
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# # Allow the user access to Post 7 for action id 3
|
|
49
|
+
# Accessly::Permission::Grant.new(user).grant!(3, Post, 7)
|
|
50
|
+
# @example
|
|
51
|
+
# # Allow the user access to Post 7 for action id 3 on a segment
|
|
52
|
+
# Accessly::Permission::Grant.new(user).on_segment(1).grant!(3, Post, 7)
|
|
53
|
+
def grant!(action_id, object_type, object_id = nil)
|
|
54
|
+
if object_id.nil?
|
|
55
|
+
general_action_grant(action_id, object_type)
|
|
56
|
+
else
|
|
57
|
+
object_action_grant(action_id, object_type, object_id)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def general_action_grant(action_id, object_type)
|
|
64
|
+
Accessly::PermittedAction.create!(
|
|
65
|
+
id: SecureRandom.uuid,
|
|
66
|
+
segment_id: @segment_id,
|
|
67
|
+
actor: @actor,
|
|
68
|
+
action: action_id,
|
|
69
|
+
object_type: String(object_type)
|
|
70
|
+
)
|
|
71
|
+
nil
|
|
72
|
+
rescue ActiveRecord::RecordNotUnique
|
|
73
|
+
nil
|
|
74
|
+
rescue => e
|
|
75
|
+
raise Accessly::GrantError.new("Could not grant action #{action_id} on object #{object_type} for actor #{@actor} because #{e}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def object_action_grant(action_id, object_type, object_id)
|
|
79
|
+
Accessly::PermittedActionOnObject.create!(
|
|
80
|
+
id: SecureRandom.uuid,
|
|
81
|
+
segment_id: @segment_id,
|
|
82
|
+
actor: @actor,
|
|
83
|
+
action: action_id,
|
|
84
|
+
object_type: String(object_type),
|
|
85
|
+
object_id: object_id
|
|
86
|
+
)
|
|
87
|
+
nil
|
|
88
|
+
rescue ActiveRecord::RecordNotUnique
|
|
89
|
+
nil
|
|
90
|
+
rescue => e
|
|
91
|
+
raise Accessly::GrantError.new("Could not grant action #{action_id} on object #{object_type} with id #{object_id} for actor #{@actor} because #{e}")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Accessly
|
|
2
|
+
module Permission
|
|
3
|
+
class Revoke < Accessly::Base
|
|
4
|
+
|
|
5
|
+
# Create an instance of Accessly::Permission::Revoke
|
|
6
|
+
# Pass in an ActiveRecord::Base for actor
|
|
7
|
+
#
|
|
8
|
+
# @param actor [ActiveRecord::Base] The actor to revoke permission
|
|
9
|
+
def initialize(actor)
|
|
10
|
+
super(actor)
|
|
11
|
+
@actor = case actor
|
|
12
|
+
when ActiveRecord::Base
|
|
13
|
+
actor
|
|
14
|
+
else
|
|
15
|
+
raise Accessly::RevokeError.new("Actor is not an ActiveRecord::Base object")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Revoke a permission for an actor.
|
|
20
|
+
# @return [nil]
|
|
21
|
+
# @overload revoke!(action_id, object_type)
|
|
22
|
+
# Revoke permission on a general action in the given namespace represented by object_type.
|
|
23
|
+
#
|
|
24
|
+
# @param action_id [Integer] The action to revoke
|
|
25
|
+
# @param object_type [String] The namespace of the given action_id.
|
|
26
|
+
# @raise [Accessly::RevokeError] if the operation does not succeed
|
|
27
|
+
# @return [nil] Returns nil if successful
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# # Remove user access to posts for action id 3
|
|
31
|
+
# Accessly::Permission::Revoke.new(user).revoke!(3, Post)
|
|
32
|
+
# @example
|
|
33
|
+
# # Remove user access to posts for action id 3 on a segment
|
|
34
|
+
# Accessly::Permission::Revoke.new(user).on_segment(1).revoke!(3, Post)
|
|
35
|
+
#
|
|
36
|
+
# @overload revoke!(action_id, object_type, object_id)
|
|
37
|
+
# Revoke permission on an ActiveRecord object.
|
|
38
|
+
#
|
|
39
|
+
# @param action_id [Integer] The action to revoke
|
|
40
|
+
# @param object_type [ActiveRecord::Base] The ActiveRecord model that removes a permission.
|
|
41
|
+
# @param object_id [Integer] The id of the ActiveRecord object that removes a permission
|
|
42
|
+
# @raise [Accessly::RevokeError] if the operation does not succeed
|
|
43
|
+
# @return [nil] Returns nil if successful
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# # Remove user access to Post 7 for action id 3
|
|
47
|
+
# Accessly::Permission::Revoke.new(user).revoke!(3, Post, 7)
|
|
48
|
+
# @example
|
|
49
|
+
# # Remove user access to Post 7 for action id 3 on a segment
|
|
50
|
+
# Accessly::Permission::Revoke.new(user).on_segment(1).revoke!(3, Post, 7)
|
|
51
|
+
def revoke!(action_id, object_type, object_id = nil)
|
|
52
|
+
if object_id.nil?
|
|
53
|
+
general_action_revoke(action_id, object_type)
|
|
54
|
+
else
|
|
55
|
+
object_action_revoke(action_id, object_type, object_id)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def general_action_revoke(action_id, object_type)
|
|
62
|
+
Accessly::PermittedAction.where(
|
|
63
|
+
segment_id: @segment_id,
|
|
64
|
+
actor: @actor,
|
|
65
|
+
action: action_id,
|
|
66
|
+
object_type: String(object_type)
|
|
67
|
+
).delete_all
|
|
68
|
+
nil
|
|
69
|
+
rescue => e
|
|
70
|
+
raise Accessly::RevokeError.new("Could not revoke action #{action_id} on object #{object_type} for actor #{@actor} because #{e}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def object_action_revoke(action_id, object_type, object_id)
|
|
74
|
+
Accessly::PermittedActionOnObject.where(
|
|
75
|
+
segment_id: @segment_id,
|
|
76
|
+
actor: @actor,
|
|
77
|
+
action: action_id,
|
|
78
|
+
object_type: String(object_type),
|
|
79
|
+
object_id: object_id
|
|
80
|
+
).delete_all
|
|
81
|
+
nil
|
|
82
|
+
rescue => e
|
|
83
|
+
raise Accessly::RevokeError.new("Could not revoke #{action_id} on object #{object_type} with id #{object_id} for actor #{@actor} because #{e}")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "accessly/query_builder"
|
|
2
|
+
|
|
3
|
+
module Accessly
|
|
4
|
+
module PermittedActions
|
|
5
|
+
class Base
|
|
6
|
+
|
|
7
|
+
def initialize(actors, segment_id)
|
|
8
|
+
@actors = actors
|
|
9
|
+
@segment_id = segment_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def past_lookups
|
|
15
|
+
@_past_lookups ||= {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def find_or_set_value(*keys, &query)
|
|
19
|
+
found_value = past_lookups.dig(*keys)
|
|
20
|
+
|
|
21
|
+
if found_value.nil?
|
|
22
|
+
found_value = query.call
|
|
23
|
+
set_value(*keys, value: found_value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
found_value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def set_value(*keys, value:)
|
|
30
|
+
lookup = past_lookups
|
|
31
|
+
keys[0..-2].each do |key|
|
|
32
|
+
lookup[key] ||= {}
|
|
33
|
+
lookup = lookup[key]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
lookup[keys[-1]] = value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Accessly
|
|
2
|
+
module PermittedActions
|
|
3
|
+
class OnObjectQuery < Base
|
|
4
|
+
|
|
5
|
+
def initialize(actors, segment_id)
|
|
6
|
+
super(actors, segment_id)
|
|
7
|
+
end
|
|
8
|
+
# Ask whether the actor has permission to perform action_id
|
|
9
|
+
# on a given record.
|
|
10
|
+
#
|
|
11
|
+
# Lookups are cached in the object to prevent redundant database calls.
|
|
12
|
+
#
|
|
13
|
+
# @param action_id [Integer, Array<Integer>] The action or actions we're checking whether the actor has. If this is an array, then the check is ORed.
|
|
14
|
+
# @param object_type [ActiveRecord::Base] The ActiveRecord model which we're checking for permission on.
|
|
15
|
+
# @param object_id [Integer] The id of the ActiveRecord object which we're checking for permission on.
|
|
16
|
+
# @return [Boolean] Returns true if actor has been granted the permission on the specified record, false otherwise.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# # Can the user perform the action with id 5 for the Post with id 7?
|
|
20
|
+
# Accessly::Query.new(user).can?(5, Post, 7)
|
|
21
|
+
# @example
|
|
22
|
+
# # Can the user perform the action with id 5 for the Post with id 7 on segment 1?
|
|
23
|
+
# Accessly::Query.new(user).on_segment(1).can?(5, Post, 7)
|
|
24
|
+
def can?(action_id, object_type, object_id)
|
|
25
|
+
find_or_set_value(action_id, object_type, object_id) do
|
|
26
|
+
Accessly::QueryBuilder.with_actors(Accessly::PermittedActionOnObject, @actors)
|
|
27
|
+
.where(
|
|
28
|
+
segment_id: @segment_id,
|
|
29
|
+
action: action_id,
|
|
30
|
+
object_type: String(object_type),
|
|
31
|
+
object_id: object_id
|
|
32
|
+
).exists?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns an ActiveRecord::Relation of ids in the namespace for
|
|
37
|
+
# which the actor has permission to perform action_id.
|
|
38
|
+
#
|
|
39
|
+
# @param action_id [Integer] The action we're checking on the actor in the namespace.
|
|
40
|
+
# @param namespace [String] The namespace to check actor permissions.
|
|
41
|
+
# @return [ActiveRecord::Relation]
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# # Give me the list of Post ids on which the user has permission to perform action_id 3
|
|
45
|
+
# Accessly::Query.new(user).list(3, Post)
|
|
46
|
+
# @example
|
|
47
|
+
# # Give me the list of Post ids on which the user has permission to perform action_id 3 on segment 1
|
|
48
|
+
# Accessly::Query.new(user).on_segment(1).list(3, Post)
|
|
49
|
+
# @example
|
|
50
|
+
# # Give me the list of Post ids on which the user and its groups has permission to perform action_id 3
|
|
51
|
+
# Accessly::Query.new(User => user.id, Group => [1,2]).list(3, Post)
|
|
52
|
+
# @example
|
|
53
|
+
# # Give me the list of Post ids on which the user and its groups has permission to perform action_id 3 on segment 1
|
|
54
|
+
# Accessly::Query.new(User => user.id, Group => [1,2]).on_segment(1).list(3, Post)
|
|
55
|
+
def list(action_id, namespace)
|
|
56
|
+
Accessly::QueryBuilder.with_actors(Accessly::PermittedActionOnObject, @actors)
|
|
57
|
+
.where(
|
|
58
|
+
segment_id: @segment_id,
|
|
59
|
+
action: Integer(action_id),
|
|
60
|
+
object_type: String(namespace),
|
|
61
|
+
).select(:object_id)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "accessly/permitted_actions/base"
|
|
2
|
+
|
|
3
|
+
module Accessly
|
|
4
|
+
module PermittedActions
|
|
5
|
+
class Query < Base
|
|
6
|
+
|
|
7
|
+
def initialize(actors, segment_id)
|
|
8
|
+
super(actors, segment_id)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Ask whether the actor has permission to perform action_id
|
|
12
|
+
# in the given namespace. Multiple actions can have the same id
|
|
13
|
+
# as long as their namespace is different. The namespace can be
|
|
14
|
+
# any String. We recommend using namespace to group a class of
|
|
15
|
+
# permissions, such as to group parts of a particular feature
|
|
16
|
+
# in your application.
|
|
17
|
+
#
|
|
18
|
+
# Lookups are cached in the object to prevent redundant database calls.
|
|
19
|
+
#
|
|
20
|
+
# @param action_id [Integer, Array<Integer>] The action or actions we're checking whether the actor has. If this is an array, then the check is ORed.
|
|
21
|
+
# @param object_type [String] The namespace of the given action_id.
|
|
22
|
+
# @return [Boolean] Returns true if actor has been granted the permission, false otherwise.
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# # Can the user perform the action with id 3 for posts?
|
|
26
|
+
# Accessly::Query.new(user).can?(3, Post)
|
|
27
|
+
# @example
|
|
28
|
+
# # Can the user perform the action with id 3 for posts on segment 1?
|
|
29
|
+
# Accessly::Query.new(user).on_segment(1).can?(3, Post)
|
|
30
|
+
def can?(action_id, object_type)
|
|
31
|
+
find_or_set_value(action_id, object_type) do
|
|
32
|
+
Accessly::QueryBuilder.with_actors(Accessly::PermittedAction, @actors)
|
|
33
|
+
.where(
|
|
34
|
+
segment_id: @segment_id,
|
|
35
|
+
action: action_id,
|
|
36
|
+
object_type: String(object_type),
|
|
37
|
+
).exists?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
module Accessly
|
|
2
|
+
module Policy
|
|
3
|
+
class Base
|
|
4
|
+
|
|
5
|
+
attr_reader :actor
|
|
6
|
+
|
|
7
|
+
def initialize(actor)
|
|
8
|
+
@actor = actor
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.actions(actions)
|
|
12
|
+
_actions.merge!(actions)
|
|
13
|
+
actions.each do |action, action_id|
|
|
14
|
+
_define_action_methods(action, action_id)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.actions_on_objects(actions_on_objects)
|
|
19
|
+
_actions_on_objects.merge!(actions_on_objects)
|
|
20
|
+
actions_on_objects.each do |action, action_id|
|
|
21
|
+
_define_action_methods(action, action_id)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.namespace
|
|
26
|
+
String(self)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def namespace
|
|
30
|
+
self.class.namespace
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.model_scope
|
|
34
|
+
raise ArgumentError.new("#model_scope is not defined on #{self.name}.")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def model_scope
|
|
38
|
+
self.class.model_scope
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def unrestricted?
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def segment_id
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def grant(action, object = nil)
|
|
50
|
+
object_id = _get_object_id(object)
|
|
51
|
+
|
|
52
|
+
action_id = if object_id.nil?
|
|
53
|
+
_get_general_action_id!(action)
|
|
54
|
+
else
|
|
55
|
+
_get_action_on_object_id!(action)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
grant_object.grant!(action_id, namespace, object_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def revoke(action, object = nil)
|
|
62
|
+
object_id = _get_object_id(object)
|
|
63
|
+
|
|
64
|
+
action_id = if object_id.nil?
|
|
65
|
+
_get_general_action_id!(action)
|
|
66
|
+
else
|
|
67
|
+
_get_action_on_object_id!(action)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
revoke_object.revoke!(action_id, namespace, object_id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def accessly_query
|
|
74
|
+
@_accessly_query ||= begin
|
|
75
|
+
query = Accessly::Query.new(actor)
|
|
76
|
+
query.on_segment(segment_id) unless segment_id.nil?
|
|
77
|
+
query
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def grant_object
|
|
82
|
+
grant_object = Accessly::Permission::Grant.new(actor)
|
|
83
|
+
grant_object.on_segment(segment_id) unless segment_id.nil?
|
|
84
|
+
|
|
85
|
+
grant_object
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def revoke_object
|
|
89
|
+
revoke_object = Accessly::Permission::Revoke.new(actor)
|
|
90
|
+
revoke_object.on_segment(segment_id) unless segment_id.nil?
|
|
91
|
+
|
|
92
|
+
revoke_object
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Determines whether the caller is trying to call an action method
|
|
98
|
+
# in the format `action_name?`. If so, this calls that method with
|
|
99
|
+
# the given arguments.
|
|
100
|
+
def method_missing(method_name, *args)
|
|
101
|
+
action_method_name = _resolve_action_method_name(method_name)
|
|
102
|
+
if action_method_name.nil?
|
|
103
|
+
super
|
|
104
|
+
else
|
|
105
|
+
send(action_method_name, *args)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Parses an action name from a given method name of the format
|
|
110
|
+
# `action_name?` or `action_name and returns the action method
|
|
111
|
+
# or the list method name. If the method name does not follow
|
|
112
|
+
# one of those formats, this assumes the caller is not calling
|
|
113
|
+
# an action or list method and returns nil.
|
|
114
|
+
def _resolve_action_method_name(method_name)
|
|
115
|
+
action_method_match = /\A(\w+)(\??)\z/.match(method_name)
|
|
116
|
+
|
|
117
|
+
return nil if action_method_match.nil? || action_method_match[1].nil?
|
|
118
|
+
|
|
119
|
+
action_name = action_method_match[1].to_sym
|
|
120
|
+
is_predicate = action_method_match[2] == "?"
|
|
121
|
+
|
|
122
|
+
if !_action_defined?(action_name)
|
|
123
|
+
nil
|
|
124
|
+
elsif is_predicate
|
|
125
|
+
_action_method_name(action_name)
|
|
126
|
+
else
|
|
127
|
+
_action_list_method_name(action_name)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The implementation for action methods follow the naming format
|
|
132
|
+
# `_resolve_action_name`. This is to allow child Policies to override
|
|
133
|
+
# the action method and still be able to call `super` when they
|
|
134
|
+
# need to call the base implementation of the action method.
|
|
135
|
+
def self._action_method_name(action_name)
|
|
136
|
+
"_resolve_#{action_name}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def _action_method_name(action_name)
|
|
140
|
+
self.class._action_method_name(action_name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The implementation for list methods follow the naming format
|
|
144
|
+
# `_list_action_name`. This is to allow child Policies to override
|
|
145
|
+
# the list method and still be able to call `super` when they
|
|
146
|
+
# need to call the base implementation of the lsit method.
|
|
147
|
+
def self._action_list_method_name(action_name)
|
|
148
|
+
"_list_#{action_name}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def _action_list_method_name(action_name)
|
|
152
|
+
self.class._action_list_method_name(action_name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Defines the action method on the Policy class for the given
|
|
156
|
+
# action name.
|
|
157
|
+
def self._define_action_methods(action, action_id)
|
|
158
|
+
unless method_defined?(_action_method_name(action))
|
|
159
|
+
define_method(_action_method_name(action)) do |*args|
|
|
160
|
+
_can_do_action?(action, action_id, args.first)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
unless method_defined?(_action_list_method_name(action))
|
|
165
|
+
define_method(_action_list_method_name(action)) do |*args|
|
|
166
|
+
_list_for_action(action, action_id)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Determines whether the caller is calling an object action
|
|
172
|
+
# method or a non-object action method and calls the appropriate
|
|
173
|
+
# implementation.
|
|
174
|
+
def _can_do_action?(action, action_id, object)
|
|
175
|
+
if object.nil?
|
|
176
|
+
_can_do_action_without_object?(action, action_id)
|
|
177
|
+
else
|
|
178
|
+
_can_do_action_with_object?(action, action_id, object)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Determines whether the actor has permission to do the action
|
|
183
|
+
# outside of an object context. If the actor should have unrestricted
|
|
184
|
+
# access, then this returns true without checking.
|
|
185
|
+
#
|
|
186
|
+
# @return [Boolean]
|
|
187
|
+
def _can_do_action_without_object?(action, action_id)
|
|
188
|
+
if _actions[action].nil?
|
|
189
|
+
_invalid_general_action!(action)
|
|
190
|
+
elsif unrestricted?
|
|
191
|
+
true
|
|
192
|
+
else
|
|
193
|
+
accessly_query.can?(action_id, namespace)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Determines whether the actor has permission to do the action
|
|
198
|
+
# on an object. If the actor should have unrestricted access,
|
|
199
|
+
# then this returns true without checking.
|
|
200
|
+
#
|
|
201
|
+
# @return [Boolean]
|
|
202
|
+
def _can_do_action_with_object?(action, action_id, object)
|
|
203
|
+
object_id = _get_object_id(object)
|
|
204
|
+
|
|
205
|
+
if _actions_on_objects[action].nil?
|
|
206
|
+
_invalid_action_on_object!(action)
|
|
207
|
+
elsif unrestricted?
|
|
208
|
+
true
|
|
209
|
+
else
|
|
210
|
+
accessly_query.can?(action_id, namespace, object_id)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def _list_for_action(action, action_id)
|
|
215
|
+
if _actions_on_objects[action].nil?
|
|
216
|
+
_invalid_action_on_object!(action)
|
|
217
|
+
elsif unrestricted?
|
|
218
|
+
model_scope
|
|
219
|
+
else
|
|
220
|
+
model_scope.where(id: accessly_query.list(action_id, namespace))
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def _get_general_action_id!(action)
|
|
225
|
+
_actions[action] || _invalid_general_action!(action)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def _get_action_on_object_id!(action)
|
|
229
|
+
_actions_on_objects[action] || _invalid_action_on_object!(action)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def _invalid_general_action!(action)
|
|
233
|
+
raise ArgumentError.new("#{action} is not defined as a general action for #{self.class.name}")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def _invalid_action_on_object!(action)
|
|
237
|
+
raise ArgumentError.new("#{action} is not defined as an action-on-object for #{self.class.name}")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def _get_object_id(object)
|
|
241
|
+
object.respond_to?(:id) ? object.id : object
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self._action_defined?(action_name)
|
|
245
|
+
_actions.include?(action_name) || _actions_on_objects.include?(action_name)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def _action_defined?(action_name)
|
|
249
|
+
self.class._action_defined?(action_name)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def self._actions
|
|
253
|
+
@@_actions ||= {}
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def _actions
|
|
257
|
+
self.class._actions
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def self._actions_on_objects
|
|
261
|
+
@@_actions_on_objects ||= {}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def _actions_on_objects
|
|
265
|
+
self.class._actions_on_objects
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|