checken 0.0.3
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/lib/checken/concerns/has_parents.rb +28 -0
- data/lib/checken/config.rb +40 -0
- data/lib/checken/dsl/group_dsl.rb +80 -0
- data/lib/checken/dsl/permission_dsl.rb +40 -0
- data/lib/checken/dsl/set_dsl.rb +65 -0
- data/lib/checken/error.rb +36 -0
- data/lib/checken/extensions/action_controller.rb +72 -0
- data/lib/checken/included_rule.rb +14 -0
- data/lib/checken/permission.rb +299 -0
- data/lib/checken/permission_group.rb +169 -0
- data/lib/checken/railtie.rb +32 -0
- data/lib/checken/reload_middleware.rb +25 -0
- data/lib/checken/rule.rb +22 -0
- data/lib/checken/rule_execution.rb +21 -0
- data/lib/checken/schema.rb +132 -0
- data/lib/checken/user.rb +36 -0
- data/lib/checken/user_proxy.rb +49 -0
- data/lib/checken/version.rb +3 -0
- data/lib/checken.rb +20 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6bdb1baf6e0ae23735e7a39a8887f8763d547ebc1729c9988db95933dce08f72
|
4
|
+
data.tar.gz: 689ace3710457f53b88a7928b02f48791ccd36d7054079de5b2faab99b50c4e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0c04c549042a1d9049a86aa485b2195af46fd2b92f68da4546455a3c180518a68c7f0135b247e24b4a49761686b2009bad89d650a3523bc52cc8fd629360145
|
7
|
+
data.tar.gz: 65b881ddbff40c87d633da457f02f925781442a13b45f648777574be0c7a5ed8cd781f1966b6c647e6d04e4c731a2d612e76f9a09bf4370d456941d4310c6745
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Checken
|
2
|
+
module Concerns
|
3
|
+
module HasParents
|
4
|
+
|
5
|
+
# Return the full path to this permission
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
def path
|
9
|
+
@key.nil? ? nil : [@group.path, @key].compact.join('.')
|
10
|
+
end
|
11
|
+
|
12
|
+
# Return the parents for ths group
|
13
|
+
#
|
14
|
+
# @return [Array<Checken::PermissionGroup, Checken::Permission>]
|
15
|
+
def parents
|
16
|
+
@key.nil? ? [] : [@group.parents, @group].compact.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return the root group
|
20
|
+
#
|
21
|
+
# @return [Checken::PermissionGroup]
|
22
|
+
def root
|
23
|
+
@key.nil? ? self : parents.first
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'checken/user_proxy'
|
3
|
+
|
4
|
+
module Checken
|
5
|
+
class Config
|
6
|
+
|
7
|
+
# The class that should be used to create user proxies.
|
8
|
+
#
|
9
|
+
# @return [Class]
|
10
|
+
def user_proxy_class
|
11
|
+
@user_proxy_class ||= UserProxy
|
12
|
+
end
|
13
|
+
attr_writer :user_proxy_class
|
14
|
+
|
15
|
+
# A logger class that will be used to log all activities
|
16
|
+
#
|
17
|
+
# @return [Logger]
|
18
|
+
def logger
|
19
|
+
@logger ||= Logger.new(log_path)
|
20
|
+
end
|
21
|
+
attr_writer :logger
|
22
|
+
|
23
|
+
# The path where logs should be written to if using the default logger
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
def log_path
|
27
|
+
@log_path ||= "/dev/null"
|
28
|
+
end
|
29
|
+
attr_writer :log_path
|
30
|
+
|
31
|
+
# The method name which will return the current user object in any
|
32
|
+
# controller action.
|
33
|
+
#
|
34
|
+
# @return [Symbol]
|
35
|
+
def current_user_method_name
|
36
|
+
@current_user_method_name || :current_user
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'checken/dsl/set_dsl'
|
2
|
+
|
3
|
+
module Checken
|
4
|
+
module DSL
|
5
|
+
class GroupDSL
|
6
|
+
|
7
|
+
def initialize(group, options = {})
|
8
|
+
@group = group
|
9
|
+
|
10
|
+
if options[:active_sets]
|
11
|
+
@active_sets = options[:active_sets]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def name(name)
|
16
|
+
@group.name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def description(description)
|
20
|
+
@group.description = description
|
21
|
+
end
|
22
|
+
|
23
|
+
def define_rule(key, *required_object_types, &block)
|
24
|
+
@group.define_rule(key, *required_object_types, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(&block)
|
28
|
+
dsl = SetDSL.new(self)
|
29
|
+
active_sets << dsl
|
30
|
+
dsl.instance_eval(&block) if block_given?
|
31
|
+
dsl
|
32
|
+
ensure
|
33
|
+
active_sets.pop
|
34
|
+
end
|
35
|
+
|
36
|
+
def group(key, &block)
|
37
|
+
sub_group = @group.groups[key.to_sym] || @group.add_group(key.to_sym)
|
38
|
+
sub_group.dsl(:active_sets => active_sets, &block) if block_given?
|
39
|
+
sub_group
|
40
|
+
end
|
41
|
+
|
42
|
+
def permission(key, description = nil, &block)
|
43
|
+
permission = @group.add_permission(key)
|
44
|
+
permission.description = description
|
45
|
+
|
46
|
+
active_sets.each do |set_dsl|
|
47
|
+
set_dsl.required_object_types.each do |rot|
|
48
|
+
permission.add_required_object_type(rot)
|
49
|
+
end
|
50
|
+
|
51
|
+
set_dsl.rules.each do |key, rule|
|
52
|
+
permission.add_rule(key, rule)
|
53
|
+
end
|
54
|
+
|
55
|
+
set_dsl.dependencies.each do |path|
|
56
|
+
permission.add_dependency(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
set_dsl.contexts.each do |context|
|
60
|
+
permission.add_context(context)
|
61
|
+
end
|
62
|
+
|
63
|
+
set_dsl.included_rules.each do |key, rule|
|
64
|
+
permission.include_rule(rule)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
permission.dsl(&block) if block_given?
|
69
|
+
permission
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def active_sets
|
75
|
+
@active_sets ||= []
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Checken
|
2
|
+
module DSL
|
3
|
+
class PermissionDSL
|
4
|
+
|
5
|
+
def initialize(permission)
|
6
|
+
@permission = permission
|
7
|
+
end
|
8
|
+
|
9
|
+
def rule(key, &block)
|
10
|
+
@permission.add_rule(key, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def depends_on(path)
|
14
|
+
@permission.add_dependency(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def include_rule(key, options = {}, &block)
|
18
|
+
@permission.include_rule(key, options, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def requires_object(*names)
|
22
|
+
names.each do |name|
|
23
|
+
@permission.add_required_object_type(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def context(*contexts)
|
28
|
+
contexts.each do |context|
|
29
|
+
@permission.add_context(context)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def context!(*contexts)
|
34
|
+
@permission.remove_all_contexts
|
35
|
+
context(*contexts)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Checken
|
2
|
+
module DSL
|
3
|
+
class SetDSL
|
4
|
+
|
5
|
+
attr_reader :rules
|
6
|
+
attr_reader :required_object_types
|
7
|
+
attr_reader :dependencies
|
8
|
+
attr_reader :contexts
|
9
|
+
attr_reader :included_rules
|
10
|
+
|
11
|
+
def initialize(group_dsl)
|
12
|
+
@group_dsl = group_dsl
|
13
|
+
@rules = {}
|
14
|
+
@required_object_types = []
|
15
|
+
@dependencies = []
|
16
|
+
@contexts = []
|
17
|
+
@included_rules = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def rule(name, &block)
|
21
|
+
@rules[name] = Rule.new(name, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def include_rule(key, options = {}, &block)
|
25
|
+
@included_rules[key] = begin
|
26
|
+
rule = IncludedRule.new(key, &block)
|
27
|
+
rule.condition = options[:if]
|
28
|
+
rule
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def requires_object(*names)
|
33
|
+
names.each do |name|
|
34
|
+
@required_object_types << name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def depends_on(*paths)
|
39
|
+
paths.each do |path|
|
40
|
+
@dependencies << path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def context(*contexts)
|
45
|
+
contexts.each do |context|
|
46
|
+
@contexts << context
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def permission(name, description = nil, &block)
|
51
|
+
@group_dsl.permission(name, description, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def group(key, &block)
|
55
|
+
# Pass the group back to the source group.
|
56
|
+
@group_dsl.group(key, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def set(&block)
|
60
|
+
@group_dsl.set(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Checken
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class PermissionNotFoundError < Error
|
6
|
+
end
|
7
|
+
|
8
|
+
class InvalidObjectError < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class NoPermissionsFoundError < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class SchemaError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class PermissionDeniedError < Error
|
18
|
+
attr_reader :code
|
19
|
+
attr_reader :description
|
20
|
+
attr_reader :permission
|
21
|
+
attr_accessor :rule
|
22
|
+
attr_accessor :user
|
23
|
+
attr_accessor :object
|
24
|
+
|
25
|
+
def initialize(code, description, permission = nil)
|
26
|
+
@code = code
|
27
|
+
@description = description
|
28
|
+
@permission = permission
|
29
|
+
@memo = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def message
|
33
|
+
"Access denied: #{description} (#{code})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Checken
|
2
|
+
module Extensions
|
3
|
+
module ActionController
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.helper_method :granted_checken_permissions
|
8
|
+
base.class_eval do
|
9
|
+
private :checken_user_proxy
|
10
|
+
private :restrict
|
11
|
+
private :granted_checken_permissions
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def checken_user_proxy
|
16
|
+
# Can be overriden to return the user proxy class which can be used
|
17
|
+
# when performing permission checks using `restrict`.
|
18
|
+
end
|
19
|
+
|
20
|
+
def restrict(permission_path, object = nil, options = {})
|
21
|
+
if checken_user_proxy.nil?
|
22
|
+
user = send(Checken.current_schema.config.current_user_method_name)
|
23
|
+
user_proxy = Checken.current_schema.config.user_proxy_class.new(user)
|
24
|
+
else
|
25
|
+
user_proxy = checken_user_proxy
|
26
|
+
end
|
27
|
+
granted_permissions = Checken.current_schema.check_permission!(permission_path, user_proxy, object)
|
28
|
+
granted_permissions.each do |permission|
|
29
|
+
granted_checken_permissions << permission
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def granted_checken_permissions
|
34
|
+
@granted_checken_permissions ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
def restrict(permission_path, object_or_options = {}, options_if_object_provided = {})
|
39
|
+
if object_or_options.is_a?(Hash)
|
40
|
+
object = nil
|
41
|
+
options = object_or_options
|
42
|
+
else
|
43
|
+
object = object_or_options
|
44
|
+
options = options_if_object_provided
|
45
|
+
end
|
46
|
+
|
47
|
+
restrict_options = options.delete(:restrict_options)
|
48
|
+
|
49
|
+
before_action(options) do
|
50
|
+
if object.is_a?(Proc)
|
51
|
+
# If a proc is given, resolve manually
|
52
|
+
resolved_object = object.call
|
53
|
+
elsif object.is_a?(Symbol)
|
54
|
+
if object.to_s =~ /\A@/
|
55
|
+
resolved_object = instance_variable_get(object.to_s)
|
56
|
+
else
|
57
|
+
resolved_object = send(object)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
# Otherwise, the object is nil
|
61
|
+
resolved_object = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
restrict(permission_path, resolved_object, restrict_options)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'checken/concerns/has_parents'
|
2
|
+
require 'checken/dsl/permission_dsl'
|
3
|
+
require 'checken/rule'
|
4
|
+
require 'checken/rule_execution'
|
5
|
+
require 'checken/included_rule'
|
6
|
+
|
7
|
+
module Checken
|
8
|
+
class Permission
|
9
|
+
|
10
|
+
include Checken::Concerns::HasParents
|
11
|
+
|
12
|
+
attr_reader :group
|
13
|
+
attr_reader :key
|
14
|
+
|
15
|
+
# A description of this permission
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
attr_accessor :description
|
19
|
+
|
20
|
+
# A list of permission paths that this permission depends on
|
21
|
+
#
|
22
|
+
# @return [Array<String>]
|
23
|
+
attr_reader :dependencies
|
24
|
+
|
25
|
+
# An array of object type names (as Strings) that the object passed to this
|
26
|
+
# permission must be one of. If empty, any object is permitted.
|
27
|
+
#
|
28
|
+
# @return [Array<String>]
|
29
|
+
attr_reader :required_object_types
|
30
|
+
|
31
|
+
# The name of the contexts that apply to this permission
|
32
|
+
#
|
33
|
+
# @return [Array<Symbol>]
|
34
|
+
attr_reader :contexts
|
35
|
+
|
36
|
+
# Create a new permission group
|
37
|
+
#
|
38
|
+
# @param group [Checken::PermissionGroup, nil]
|
39
|
+
# @param key [Symbol]
|
40
|
+
def initialize(group, key)
|
41
|
+
if group.nil?
|
42
|
+
raise Error, "Group must be provided when creating a permission"
|
43
|
+
end
|
44
|
+
|
45
|
+
@group = group
|
46
|
+
@key = key
|
47
|
+
@required_object_types = []
|
48
|
+
@dependencies = []
|
49
|
+
@contexts = []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a description
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
def description
|
56
|
+
@description || "#{path}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check this permission and raises an error if not permitted.
|
60
|
+
#
|
61
|
+
# @param user [Object]
|
62
|
+
# @param object [Object]
|
63
|
+
# @raises [Checken::PermissionDeniedError]
|
64
|
+
# @raises [Checken::InvalidObjectError]
|
65
|
+
# @return true
|
66
|
+
def check!(user_proxy, object = nil)
|
67
|
+
# If we havent' been given a user proxy here, we need to make one. This
|
68
|
+
# shouldn't happen very often in production because everything would be
|
69
|
+
# encapsulated by the User#can? method.
|
70
|
+
unless user_proxy.is_a?(Checken::UserProxy)
|
71
|
+
user_proxy = @group.schema.config.user_proxy_class.new(user_proxy)
|
72
|
+
end
|
73
|
+
|
74
|
+
# If we're asking about this permission and we aren't in the correct
|
75
|
+
# context, it should be denied always.
|
76
|
+
unless @contexts.empty?
|
77
|
+
unless @contexts.any? { |c| user_proxy.contexts.include?(c) }
|
78
|
+
@group.schema.logger.info "`#{self.path}` not granted to #{user_proxy.description} because not in context."
|
79
|
+
error = PermissionDeniedError.new('NotInContext', "Permission '#{self.path}' cannot be granted in the #{user_proxy.contexts.join(',')} context(s). Only allowed for #{@contexts.join(', ')}.", self)
|
80
|
+
error.user = user_proxy.user
|
81
|
+
error.object = object
|
82
|
+
raise error
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check the user has this permission
|
87
|
+
unless user_proxy.granted_permissions.include?(self.path)
|
88
|
+
@group.schema.logger.info "`#{self.path}` not granted to #{user_proxy.description}"
|
89
|
+
error = PermissionDeniedError.new('PermissionNotGranted', "User has not been granted the '#{self.path}' permission", self)
|
90
|
+
error.user = user_proxy.user
|
91
|
+
error.object = object
|
92
|
+
raise error
|
93
|
+
end
|
94
|
+
|
95
|
+
# Check other dependent rules once we've established this
|
96
|
+
# user has the base rule. The actual rules won't be checked
|
97
|
+
# until we've checked other rules.
|
98
|
+
dependencies_as_permissions.each do |dependency_permission|
|
99
|
+
@group.schema.logger.info "`#{self.path}` has a dependency of `#{dependency_permission.path}`..."
|
100
|
+
dependency_permission.check!(user_proxy, object)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check any included rules too
|
104
|
+
if unsatisifed_rule = self.first_unsatisfied_included_rule(user_proxy, object)
|
105
|
+
@group.schema.logger.info "`#{self.path} not granted to #{user_proxy.description} because rule `#{unsatisifed_rule.rule.key}` on `#{self.path}` was not satisified."
|
106
|
+
error = PermissionDeniedError.new('IncludedRuleNotSatisifed', "Rule #{unsatisifed_rule.rule.key} (on #{self.path}) was not satisified.", self)
|
107
|
+
error.rule = unsatisifed_rule
|
108
|
+
error.user = user_proxy.user
|
109
|
+
error.object = object
|
110
|
+
raise error
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check rules
|
114
|
+
if self.required_object_types.empty? || self.required_object_types.include?(object.class.name)
|
115
|
+
if unsatisifed_rule = self.first_unsatisfied_rule(user_proxy, object)
|
116
|
+
@group.schema.logger.info "`#{self.path} not granted to #{user_proxy.description} because rule `#{unsatisifed_rule.rule.key}` on `#{self.path}` was not satisified."
|
117
|
+
error = PermissionDeniedError.new('RuleNotSatisifed', "Rule #{unsatisifed_rule.rule.key} (on #{self.path}) was not satisified.", self)
|
118
|
+
error.rule = unsatisifed_rule
|
119
|
+
error.user = user_proxy.user
|
120
|
+
error.object = object
|
121
|
+
raise error
|
122
|
+
else
|
123
|
+
@group.schema.logger.info "`#{self.path}` granted to #{user_proxy.description}"
|
124
|
+
[self, *dependencies_as_permissions]
|
125
|
+
end
|
126
|
+
else
|
127
|
+
# If one of the permission doesn't have the right object type, raise an error
|
128
|
+
raise InvalidObjectError, "The #{object.class.name} object provided to permission check for #{self.path} was not valid. Valid object types are: #{self.required_object_types.join(', ')}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return a hash of all configured rules
|
133
|
+
#
|
134
|
+
# @return [Hash]
|
135
|
+
def rules
|
136
|
+
@rules ||= {}
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add a new rule to this permission
|
140
|
+
#
|
141
|
+
# @param key [String]
|
142
|
+
# @return [Checken::Rule]
|
143
|
+
def add_rule(key, rule = nil, &block)
|
144
|
+
key = key.to_sym
|
145
|
+
if rules[key].nil?
|
146
|
+
rule ||= Rule.new(key, &block)
|
147
|
+
rules[key] = rule
|
148
|
+
else
|
149
|
+
raise Error, "Rule with key '#{key}' already exists on this permission"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Return a hash of all configured included rules
|
154
|
+
#
|
155
|
+
# @return [Hash]
|
156
|
+
def included_rules
|
157
|
+
@included_rules ||= {}
|
158
|
+
end
|
159
|
+
|
160
|
+
# Add a new rule to this permission
|
161
|
+
#
|
162
|
+
# @param key [String]
|
163
|
+
# @return [Checken::Rule]
|
164
|
+
def include_rule(key_or_existing_rule, options = {}, &block)
|
165
|
+
if key_or_existing_rule.is_a?(IncludedRule)
|
166
|
+
key = key_or_existing_rule.key
|
167
|
+
included_rule = key_or_existing_rule
|
168
|
+
else
|
169
|
+
key = key_or_existing_rule.to_sym
|
170
|
+
included_rule = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
if included_rules[key].nil?
|
174
|
+
included_rule ||= begin
|
175
|
+
new_rule = IncludedRule.new(key, &block)
|
176
|
+
new_rule.condition = options[:if]
|
177
|
+
new_rule
|
178
|
+
end
|
179
|
+
included_rules[key] = included_rule
|
180
|
+
else
|
181
|
+
raise Error, "Rule with key '#{key}' already been included on this permission"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Add a new context to this permission
|
186
|
+
#
|
187
|
+
# @param context [Symbol]
|
188
|
+
# @return [Symbol, false]
|
189
|
+
def add_context(context)
|
190
|
+
context = context.to_sym
|
191
|
+
if self.contexts.include?(context)
|
192
|
+
false
|
193
|
+
else
|
194
|
+
self.contexts << context
|
195
|
+
context
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Remove all context from this permission
|
200
|
+
#
|
201
|
+
# @return [Integer]
|
202
|
+
def remove_all_contexts
|
203
|
+
previous_size = @contexts.size
|
204
|
+
@contexts = []
|
205
|
+
previous_size
|
206
|
+
end
|
207
|
+
|
208
|
+
# Add a new dependency to this permission
|
209
|
+
#
|
210
|
+
# @param path [String]
|
211
|
+
# @return [String, false]
|
212
|
+
def add_dependency(path)
|
213
|
+
path = path.to_s
|
214
|
+
if dependencies.include?(path)
|
215
|
+
false
|
216
|
+
else
|
217
|
+
dependencies << path
|
218
|
+
path
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Add a new dependency to this permission
|
223
|
+
#
|
224
|
+
# @param path [String]
|
225
|
+
# @return [String, false]
|
226
|
+
def add_required_object_type(type)
|
227
|
+
type = type.to_s
|
228
|
+
if required_object_types.include?(type)
|
229
|
+
false
|
230
|
+
else
|
231
|
+
required_object_types << type
|
232
|
+
type
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Check all the rules for this permission and ensure they are compliant.
|
237
|
+
#
|
238
|
+
# @param [Checken::UserProxy]
|
239
|
+
# @param [Object]
|
240
|
+
# @return [Checken::Rule, false] false if all rules are satisified
|
241
|
+
def first_unsatisfied_rule(user_proxy, object)
|
242
|
+
self.rules.values.each do |rule|
|
243
|
+
rule_execution = RuleExecution.new(rule, user_proxy.user, object)
|
244
|
+
unless rule_execution.satisfied?
|
245
|
+
return rule_execution
|
246
|
+
end
|
247
|
+
end
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
|
251
|
+
def first_unsatisfied_included_rule(user_proxy, object)
|
252
|
+
self.included_rules.values.each do |included_rule|
|
253
|
+
|
254
|
+
if included_rule.condition && !included_rule.condition.call(user_proxy.user, object)
|
255
|
+
# If the inclusion has a condition, check that and skip this
|
256
|
+
# included rule if it's not valid.
|
257
|
+
next
|
258
|
+
end
|
259
|
+
|
260
|
+
if included_rule.block
|
261
|
+
translated_object = included_rule.block.call(object)
|
262
|
+
else
|
263
|
+
translated_object = object
|
264
|
+
end
|
265
|
+
|
266
|
+
rule = @group.all_defined_rules[included_rule.key]
|
267
|
+
if rule.nil?
|
268
|
+
raise Error, "No defined rule with key #{included_rule.key} is available for #{self.path}"
|
269
|
+
end
|
270
|
+
|
271
|
+
unless rule.required_object_types.empty? || rule.required_object_types.include?(translated_object.class.name)
|
272
|
+
raise InvalidObjectError, "The #{translated_object.class.name} object provided to included rule (#{rule.key}) for #{self.path} was not valid. Valid object types are: #{rule.required_object_types.join(', ')}"
|
273
|
+
end
|
274
|
+
|
275
|
+
rule_execution = RuleExecution.new(rule, user_proxy.user, translated_object)
|
276
|
+
unless rule_execution.satisfied?
|
277
|
+
return rule_execution
|
278
|
+
end
|
279
|
+
end
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def dsl(&block)
|
284
|
+
dsl = DSL::PermissionDSL.new(self)
|
285
|
+
dsl.instance_eval(&block) if block_given?
|
286
|
+
dsl
|
287
|
+
end
|
288
|
+
|
289
|
+
# Return an array of all dependencies as permissions
|
290
|
+
#
|
291
|
+
# @return [Array<Checken::Permission>]
|
292
|
+
def dependencies_as_permissions
|
293
|
+
@dependencies_as_permissions ||= dependencies.map do |path|
|
294
|
+
@group.schema.root_group.find_permissions_from_path(path)
|
295
|
+
end.flatten
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'checken/permission'
|
2
|
+
require 'checken/error'
|
3
|
+
require 'checken/concerns/has_parents'
|
4
|
+
require 'checken/dsl/group_dsl'
|
5
|
+
|
6
|
+
module Checken
|
7
|
+
class PermissionGroup
|
8
|
+
|
9
|
+
include Checken::Concerns::HasParents
|
10
|
+
|
11
|
+
attr_accessor :name
|
12
|
+
attr_accessor :description
|
13
|
+
attr_reader :schema
|
14
|
+
attr_reader :group
|
15
|
+
attr_reader :key
|
16
|
+
attr_reader :groups
|
17
|
+
attr_reader :permissions
|
18
|
+
attr_reader :defined_rules
|
19
|
+
|
20
|
+
# Return a group or permission matching the given key
|
21
|
+
#
|
22
|
+
# @param group_or_permission_key [Symbol]
|
23
|
+
# @return [Checken::PermissionGroup, Checken::Permission, nil]
|
24
|
+
def [](group_or_permission_key)
|
25
|
+
@groups[group_or_permission_key.to_sym] || @permissions[group_or_permission_key.to_sym]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create a new permission group
|
29
|
+
#
|
30
|
+
# @param group [Checken::PermissionGroup, nil]
|
31
|
+
# @param key [Symbol]
|
32
|
+
def initialize(schema, group, key = nil)
|
33
|
+
if group && key.nil?
|
34
|
+
raise Error, "Cannot create a new non-root permission group without a key"
|
35
|
+
elsif group.nil? && key
|
36
|
+
raise Error, "Cannot create a new root permission group with a key"
|
37
|
+
end
|
38
|
+
|
39
|
+
@schema = schema
|
40
|
+
@group = group
|
41
|
+
@key = key.to_sym if key
|
42
|
+
@groups = {}
|
43
|
+
@permissions = {}
|
44
|
+
@defined_rules = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return a group or a permission that matches the given key
|
48
|
+
#
|
49
|
+
# @return [Cheken::PermissionGroup, Checken::Permission]
|
50
|
+
def group_or_permission(key)
|
51
|
+
key = key.to_sym
|
52
|
+
@groups[key] || @permissions[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Adds a new sub group to this group
|
56
|
+
#
|
57
|
+
# @param key [String]
|
58
|
+
# @return [Checken::PermissionGroup]
|
59
|
+
def add_group(key)
|
60
|
+
key = key.to_sym
|
61
|
+
if group_or_permission(key).nil?
|
62
|
+
@groups[key] = PermissionGroup.new(@schema, self, key)
|
63
|
+
else
|
64
|
+
raise Error, "Group or permission with key of #{key} already exists"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Adds a permission to the group
|
69
|
+
#
|
70
|
+
# @param key [String]
|
71
|
+
# @return [Checken::Permission]
|
72
|
+
def add_permission(key)
|
73
|
+
key = key.to_sym
|
74
|
+
if group_or_permission(key).nil?
|
75
|
+
@permissions[key] = Permission.new(self, key)
|
76
|
+
else
|
77
|
+
raise Error, "Group or permission with key of #{key} already exists"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define a new global rule that can be used by any permissions
|
82
|
+
#
|
83
|
+
# @param key [Symbol]
|
84
|
+
# @param required_object_types [Array]
|
85
|
+
# @return [Checken::Rule]
|
86
|
+
def define_rule(key, *required_object_types, &block)
|
87
|
+
if all_defined_rules[key.to_sym]
|
88
|
+
raise Checken::Error, "Rule #{key} has already been defined"
|
89
|
+
else
|
90
|
+
rule = Rule.new(key, &block)
|
91
|
+
required_object_types.each { |rot| rule.required_object_types << rot }
|
92
|
+
@defined_rules[key.to_sym] = rule
|
93
|
+
rule
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return an array of all defined rules on this and all upper groups
|
98
|
+
#
|
99
|
+
# @return [Array<Checken::Rule>]
|
100
|
+
def all_defined_rules
|
101
|
+
if @group
|
102
|
+
@defined_rules.merge(@group.all_defined_rules)
|
103
|
+
else
|
104
|
+
@defined_rules
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Find permissions from a path
|
109
|
+
def find_permissions_from_path(path)
|
110
|
+
unless path.is_a?(String) && path.length > 0
|
111
|
+
raise PermissionNotFoundError, "Must provide a permission path"
|
112
|
+
end
|
113
|
+
|
114
|
+
path_parts = path.split('.').map(&:to_sym)
|
115
|
+
last_group_or_permission = self
|
116
|
+
while part = path_parts.shift
|
117
|
+
if part == :*
|
118
|
+
if path_parts.empty?
|
119
|
+
# We're at the end of the path, that's an acceptable place for a wildcard.
|
120
|
+
# Return all the permissions in the final group.
|
121
|
+
return last_group_or_permission.permissions.values
|
122
|
+
else
|
123
|
+
raise Error, "Wildcards must be placed at the end of a permission path"
|
124
|
+
end
|
125
|
+
elsif part == :** && path_parts[0] == :*
|
126
|
+
# If we get a **.* wildcard, we should find permissions in the sub groups too.
|
127
|
+
return last_group_or_permission.all_permissions
|
128
|
+
else
|
129
|
+
last_group_or_permission = last_group_or_permission.group_or_permission(part)
|
130
|
+
if last_group_or_permission.is_a?(Permission) && !path_parts.empty?
|
131
|
+
raise Error, "Permission found too early in the path. Permission key should always be at the end of the path."
|
132
|
+
elsif last_group_or_permission.nil?
|
133
|
+
raise PermissionNotFoundError, "No permission found matching '#{path}'"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
if last_group_or_permission.is_a?(Permission)
|
139
|
+
[last_group_or_permission]
|
140
|
+
else
|
141
|
+
raise Error, "Last part of path was not a permission. Last part of permission must be a path"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return all permissions in this group and all the permissions in its sub groups
|
146
|
+
#
|
147
|
+
# @return [Array<Checken::Permission>]
|
148
|
+
def all_permissions
|
149
|
+
array = []
|
150
|
+
@permissions.each { |_, permission| array << permission }
|
151
|
+
@groups.each do |_, group|
|
152
|
+
group.all_permissions.each do |permission|
|
153
|
+
array << permission
|
154
|
+
end
|
155
|
+
end
|
156
|
+
array
|
157
|
+
end
|
158
|
+
|
159
|
+
# Execute the given block within the group DSL
|
160
|
+
#
|
161
|
+
# @return [Checken::DSL::GroupDSL]
|
162
|
+
def dsl(options = {}, &block)
|
163
|
+
dsl = DSL::GroupDSL.new(self, options)
|
164
|
+
dsl.instance_eval(&block)
|
165
|
+
dsl
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'checken/schema'
|
2
|
+
require 'checken/reload_middleware'
|
3
|
+
|
4
|
+
module Checken
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
|
7
|
+
initializer 'checken.initialize' do |app|
|
8
|
+
# Initialize a new schema for the application when it is loaded.
|
9
|
+
Checken::Schema.instance = Checken::Schema.new
|
10
|
+
|
11
|
+
# Default configuration
|
12
|
+
Checken::Schema.instance.configure do |config|
|
13
|
+
# Set the logger to log into a file in the log directory.
|
14
|
+
# This can be overriden later if needed.
|
15
|
+
config.log_path = Rails.root.join('log', 'checken.log')
|
16
|
+
end
|
17
|
+
|
18
|
+
# Load from a directory
|
19
|
+
Checken::Schema.instance.load_from_directory(Rails.root.join('permissions'))
|
20
|
+
|
21
|
+
# Add controller options
|
22
|
+
ActiveSupport.on_load :action_controller do
|
23
|
+
require 'checken/extensions/action_controller'
|
24
|
+
include Checken::Extensions::ActionController
|
25
|
+
end
|
26
|
+
|
27
|
+
# Insert the middleware
|
28
|
+
app.middleware.insert_before(ActionDispatch::Callbacks, Checken::ReloadMiddleware)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'checken/schema'
|
2
|
+
|
3
|
+
module Checken
|
4
|
+
class ReloadMiddleware
|
5
|
+
|
6
|
+
MUTEX = Mutex.new
|
7
|
+
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
# If we need to reload, we shall do that here.
|
14
|
+
unless Rails.application.config.cache_classes
|
15
|
+
MUTEX.synchronize do
|
16
|
+
Checken::Schema.instance.reload
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Call our app as normal
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/checken/rule.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Checken
|
2
|
+
class Rule
|
3
|
+
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :required_object_types
|
6
|
+
|
7
|
+
def initialize(key, &block)
|
8
|
+
@key = key
|
9
|
+
@block = block
|
10
|
+
@required_object_types = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Are we satisifed that this rule's condition is true?
|
14
|
+
#
|
15
|
+
# @param user [Checken::User]
|
16
|
+
# @return [Boolean]
|
17
|
+
def satisfied?(rule_execution)
|
18
|
+
!!@block.call(rule_execution.user, rule_execution.object, rule_execution)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Checken
|
2
|
+
class RuleExecution
|
3
|
+
|
4
|
+
attr_reader :rule
|
5
|
+
attr_reader :user
|
6
|
+
attr_reader :object
|
7
|
+
attr_reader :memo
|
8
|
+
|
9
|
+
def initialize(rule, user, object = nil)
|
10
|
+
@rule = rule
|
11
|
+
@user = user
|
12
|
+
@object = object
|
13
|
+
@memo = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def satisfied?
|
17
|
+
@rule.satisfied?(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'checken/config'
|
2
|
+
require 'checken/permission'
|
3
|
+
require 'checken/permission_group'
|
4
|
+
|
5
|
+
module Checken
|
6
|
+
class Schema
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# This can be used for storing a global instance of a schema for an application
|
10
|
+
# that may require such a thing.
|
11
|
+
attr_accessor :instance
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :root_group
|
15
|
+
attr_reader :config
|
16
|
+
|
17
|
+
# Create a new schema
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
@root_group = PermissionGroup.new(self, nil)
|
21
|
+
@config = Config.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add configuration for this schema
|
25
|
+
def configure(&block)
|
26
|
+
block.call(@config)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Does the given user have the appropriate permissions to handle?
|
30
|
+
#
|
31
|
+
# @param permission_path [String]
|
32
|
+
# @param user [User]
|
33
|
+
# @param object [Object]
|
34
|
+
def check_permission!(permission_path, user_proxy, object = nil)
|
35
|
+
permissions = @root_group.find_permissions_from_path(permission_path)
|
36
|
+
|
37
|
+
if permissions.size == 1
|
38
|
+
# If we only have a single permission, we'll just run the check
|
39
|
+
# as normal through the check process. This will work as normal and raise
|
40
|
+
# and return directly.
|
41
|
+
permissions.first.check!(user_proxy, object)
|
42
|
+
|
43
|
+
elsif permissions.size == 0
|
44
|
+
# No permissions found
|
45
|
+
raise Checken::NoPermissionsFoundError, "No permissions found matching #{permission_path}"
|
46
|
+
|
47
|
+
else
|
48
|
+
# If we have multiple permissions, we need to loop through each permission
|
49
|
+
# and handle them as appropriate.
|
50
|
+
granted_permissions = []
|
51
|
+
ungranted_permissions = 0
|
52
|
+
permissions.each do |permission|
|
53
|
+
begin
|
54
|
+
permission.check!(user_proxy, object).each do |permission|
|
55
|
+
granted_permissions << permission
|
56
|
+
end
|
57
|
+
rescue Checken::PermissionDeniedError => e
|
58
|
+
if e.code == 'PermissionNotGranted'
|
59
|
+
# If the permission isn't granted, update the counter so we can
|
60
|
+
# keep track of the number of ungranted permissions.
|
61
|
+
ungranted_permissions += 1
|
62
|
+
else
|
63
|
+
# Raise other errors as normal
|
64
|
+
raise
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if permissions.size == ungranted_permissions
|
70
|
+
# If the user is ungranted to all the found permissions, they do not
|
71
|
+
# have access and should be denied.
|
72
|
+
raise PermissionDeniedError.new('PermissionNotGranted', "User does not have any permissions #{permissions.map(&:path).join(', ')} permission.", permissions.first)
|
73
|
+
else
|
74
|
+
granted_permissions
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Load a set of schema files from a given directory
|
80
|
+
#
|
81
|
+
# @param path [String]
|
82
|
+
# @return [Boolean]
|
83
|
+
def load_from_directory(path)
|
84
|
+
# Store the load path for future reload
|
85
|
+
@load_path = path
|
86
|
+
|
87
|
+
# If the path doesn't exist, just return false. We won't load anything
|
88
|
+
# if the directory hasnt' been loaded yet.
|
89
|
+
unless File.exist?(path)
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check that the directory is a a directory
|
94
|
+
unless File.directory?(path)
|
95
|
+
raise Error, "Path to directory must be a directory. #{path} is not a directory."
|
96
|
+
end
|
97
|
+
|
98
|
+
# Read all the files and pass them through the DSL for the root schema.
|
99
|
+
# Each directory is a group. Everything in the root will be at the root.
|
100
|
+
Dir[File.join(path, "**", "*.rb")].each do |path|
|
101
|
+
contents = File.read(path)
|
102
|
+
dsl = DSL::GroupDSL.new(@root_group)
|
103
|
+
dsl.instance_eval(contents, path)
|
104
|
+
end
|
105
|
+
|
106
|
+
logger.info "Loaded permission schema from #{path}"
|
107
|
+
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Reload the schema from the directory if possible
|
112
|
+
#
|
113
|
+
# @return [void]
|
114
|
+
def reload
|
115
|
+
if @load_path
|
116
|
+
@root_group = PermissionGroup.new(self, nil)
|
117
|
+
load_from_directory(@load_path)
|
118
|
+
true
|
119
|
+
else
|
120
|
+
raise Error, "Cannot reload a schema that wasn't loaded from a directory"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return the logger
|
125
|
+
#
|
126
|
+
# @return [Logger]
|
127
|
+
def logger
|
128
|
+
@config.logger
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
data/lib/checken/user.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Checken
|
2
|
+
module User
|
3
|
+
|
4
|
+
# Can the user perform the given action?
|
5
|
+
#
|
6
|
+
# @param permission_path [String] the permission name/path
|
7
|
+
# @option options [Checken::Schema] :schema an optional scheme to use
|
8
|
+
# @return [Boolean]
|
9
|
+
def check_permission!(permission_path, object_or_options = {}, options_when_object_provided = {})
|
10
|
+
if object_or_options.is_a?(Hash)
|
11
|
+
object = nil
|
12
|
+
options = object_or_options
|
13
|
+
else
|
14
|
+
object = object_or_options
|
15
|
+
options = options_when_object_provided
|
16
|
+
end
|
17
|
+
|
18
|
+
schema = options.delete(:schema) || Checken.current_schema || Checken::Schema.instance
|
19
|
+
|
20
|
+
if schema.nil?
|
21
|
+
raise Error, "Could not determine a schema. Make sure you set Checken.current_schema or pass :schema to can? methods."
|
22
|
+
end
|
23
|
+
|
24
|
+
user_proxy = schema.config.user_proxy_class.new(self)
|
25
|
+
schema.check_permission!(permission_path, user_proxy, object)
|
26
|
+
end
|
27
|
+
|
28
|
+
def can?(*args)
|
29
|
+
check_permission!(*args)
|
30
|
+
true
|
31
|
+
rescue Checken::PermissionDeniedError => e
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Checken
|
2
|
+
# The user proxy class sits on top of a user and provides the methods that
|
3
|
+
# checken needs. You can write your own proxy or use the default. If you use
|
4
|
+
# the default you'll need to make sure that the users you provide implement
|
5
|
+
# the methods this proxy will call.
|
6
|
+
#
|
7
|
+
# All interactions between checken and a user will happen via a proxy. This
|
8
|
+
# default class is a useful benchmark list of how your user should behave.
|
9
|
+
class UserProxy
|
10
|
+
|
11
|
+
attr_accessor :user
|
12
|
+
|
13
|
+
# @param user [Object]
|
14
|
+
def initialize(user)
|
15
|
+
@user = user
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return a suitable description for this user for use in log files
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def description
|
22
|
+
if @user.respond_to?(:id)
|
23
|
+
"#{@user.class}##{@user.id}"
|
24
|
+
else
|
25
|
+
"#{@user.class}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an array of permissions that this user has permission to
|
30
|
+
# use.
|
31
|
+
#
|
32
|
+
# @return [Array<String>]
|
33
|
+
def granted_permissions
|
34
|
+
@user.assigned_checken_permissions
|
35
|
+
end
|
36
|
+
|
37
|
+
# An array of contexts that this user is part of
|
38
|
+
#
|
39
|
+
# @return [Array<Symbol>]
|
40
|
+
def contexts
|
41
|
+
if @user.respond_to?(:checken_contexts)
|
42
|
+
@user.checken_contexts
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/checken.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'checken/version'
|
2
|
+
require 'checken/user'
|
3
|
+
|
4
|
+
module Checken
|
5
|
+
|
6
|
+
# Return the current global scheme
|
7
|
+
def self.current_schema
|
8
|
+
Thread.current[:cheken_schema] || Checken::Schema.instance
|
9
|
+
end
|
10
|
+
|
11
|
+
# Set the current global schema
|
12
|
+
def self.current_schema=(schema)
|
13
|
+
Thread.current[:cheken_schema] = schema
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
if defined?(Rails)
|
19
|
+
require 'checken/railtie'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: checken
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: An authorization framework for Ruby & Rails applications.
|
14
|
+
email:
|
15
|
+
- adam@krystal.io
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/checken.rb
|
21
|
+
- lib/checken/concerns/has_parents.rb
|
22
|
+
- lib/checken/config.rb
|
23
|
+
- lib/checken/dsl/group_dsl.rb
|
24
|
+
- lib/checken/dsl/permission_dsl.rb
|
25
|
+
- lib/checken/dsl/set_dsl.rb
|
26
|
+
- lib/checken/error.rb
|
27
|
+
- lib/checken/extensions/action_controller.rb
|
28
|
+
- lib/checken/included_rule.rb
|
29
|
+
- lib/checken/permission.rb
|
30
|
+
- lib/checken/permission_group.rb
|
31
|
+
- lib/checken/railtie.rb
|
32
|
+
- lib/checken/reload_middleware.rb
|
33
|
+
- lib/checken/rule.rb
|
34
|
+
- lib/checken/rule_execution.rb
|
35
|
+
- lib/checken/schema.rb
|
36
|
+
- lib/checken/user.rb
|
37
|
+
- lib/checken/user_proxy.rb
|
38
|
+
- lib/checken/version.rb
|
39
|
+
homepage: https://github.com/krystal/checken
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata:
|
43
|
+
rubygems_mfa_required: 'false'
|
44
|
+
changelog_uri: https://github.com/krystal/checken/CHANGELOG.md
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.5.22
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: This gem provides a friendly DSL for managing and enforcing permissions.
|
64
|
+
test_files: []
|