casbin-ruby 1.0.3 → 1.0.4
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 +4 -4
- data/README.md +242 -0
- data/lib/casbin-ruby.rb +11 -0
- data/lib/casbin-ruby/config/config.rb +115 -0
- data/lib/casbin-ruby/core_enforcer.rb +356 -0
- data/lib/casbin-ruby/effect/allow_and_deny_effector.rb +23 -0
- data/lib/casbin-ruby/effect/allow_override_effector.rb +23 -0
- data/lib/casbin-ruby/effect/default_effector.rb +37 -0
- data/lib/casbin-ruby/effect/deny_override_effector.rb +23 -0
- data/lib/casbin-ruby/effect/effector.rb +18 -0
- data/lib/casbin-ruby/effect/priority_effector.rb +25 -0
- data/lib/casbin-ruby/enforcer.rb +189 -0
- data/lib/casbin-ruby/internal_enforcer.rb +73 -0
- data/lib/casbin-ruby/management_enforcer.rb +297 -0
- data/lib/casbin-ruby/model/assertion.rb +33 -0
- data/lib/casbin-ruby/model/function_map.rb +30 -0
- data/lib/casbin-ruby/model/model.rb +80 -0
- data/lib/casbin-ruby/model/policy.rb +161 -0
- data/lib/casbin-ruby/persist/adapter.rb +39 -0
- data/lib/casbin-ruby/persist/adapters/file_adapter.rb +53 -0
- data/lib/casbin-ruby/persist/batch_adapter.rb +16 -0
- data/lib/casbin-ruby/persist/filtered_adapter.rb +17 -0
- data/lib/casbin-ruby/rbac/default_role_manager/role.rb +54 -0
- data/lib/casbin-ruby/rbac/default_role_manager/role_manager.rb +146 -0
- data/lib/casbin-ruby/rbac/role_manager.rb +22 -0
- data/lib/casbin-ruby/synced_enforcer.rb +39 -0
- data/lib/casbin-ruby/util.rb +80 -0
- data/lib/casbin-ruby/util/builtin_operators.rb +105 -0
- data/lib/casbin-ruby/util/evaluator.rb +27 -0
- data/lib/casbin-ruby/util/thread_lock.rb +19 -0
- data/lib/casbin-ruby/version.rb +5 -0
- data/spec/casbin/config/config_spec.rb +66 -0
- data/spec/casbin/core_enforcer_spec.rb +473 -0
- data/spec/casbin/enforcer_spec.rb +302 -0
- data/spec/casbin/model/function_map_spec.rb +28 -0
- data/spec/casbin/rbac/default_role_manager/role_manager_spec.rb +131 -0
- data/spec/casbin/rbac/default_role_manager/role_spec.rb +84 -0
- data/spec/casbin/util/builtin_operators_spec.rb +205 -0
- data/spec/casbin/util_spec.rb +98 -0
- data/spec/support/model_helper.rb +9 -0
- metadata +51 -3
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Casbin
|
6
|
+
module Model
|
7
|
+
class Assertion
|
8
|
+
attr_accessor :key, :value, :tokens, :policy, :rm
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
def initialize(hash = {})
|
12
|
+
@key = hash[:key].to_s
|
13
|
+
@value = hash[:value].to_s
|
14
|
+
@tokens = [*hash[:tokens]]
|
15
|
+
@policy = [*hash[:policy]]
|
16
|
+
@logger = hash[:logger] || Logger.new($stdout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_role_links(rm)
|
20
|
+
@rm = rm
|
21
|
+
count = value.count('_')
|
22
|
+
policy.each do |rule|
|
23
|
+
raise 'the number of "_" in role definition should be at least 2' if count < 2
|
24
|
+
raise 'grouping policy elements do not meet role definition' if rule.size < count
|
25
|
+
|
26
|
+
rm.add_link(*rule)
|
27
|
+
logger.info("Role links for: #{key}")
|
28
|
+
rm.print_roles
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Casbin
|
4
|
+
module Model
|
5
|
+
class FunctionMap
|
6
|
+
def initialize
|
7
|
+
@fm = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :fm
|
11
|
+
alias get_functions fm
|
12
|
+
|
13
|
+
def add_function(name, func)
|
14
|
+
fm[name] = func
|
15
|
+
end
|
16
|
+
|
17
|
+
# It might be better to move this to initialize
|
18
|
+
def self.load_function_map
|
19
|
+
fm = FunctionMap.new
|
20
|
+
fm.add_function('keyMatch', ->(*args) { Util::BuiltinOperators.key_match_func(*args) })
|
21
|
+
fm.add_function('keyMatch2', ->(*args) { Util::BuiltinOperators.key_match2_func(*args) })
|
22
|
+
fm.add_function('regexMatch', ->(*args) { Util::BuiltinOperators.regex_match_func(*args) })
|
23
|
+
fm.add_function('ipMatch', ->(*args) { Util::BuiltinOperators.ip_match_func(*args) })
|
24
|
+
fm.add_function('globMatch', ->(*args) { Util::BuiltinOperators.glob_match_func(*args) })
|
25
|
+
|
26
|
+
fm
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'casbin-ruby/model/policy'
|
4
|
+
require 'casbin-ruby/model/assertion'
|
5
|
+
require 'casbin-ruby/config/config'
|
6
|
+
require 'casbin-ruby/util'
|
7
|
+
|
8
|
+
module Casbin
|
9
|
+
module Model
|
10
|
+
class Model < Model::Policy
|
11
|
+
SECTION_NAME_MAP = {
|
12
|
+
r: 'request_definition',
|
13
|
+
p: 'policy_definition',
|
14
|
+
g: 'role_definition',
|
15
|
+
e: 'policy_effect',
|
16
|
+
m: 'matchers'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def load_model(path)
|
20
|
+
cfg = Config::Config.new_config(path)
|
21
|
+
load_sections(cfg)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_model_from_text(text)
|
25
|
+
cfg = Config::Config.new_config_from_text(text)
|
26
|
+
load_sections(cfg)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_def(sec, key, value)
|
30
|
+
return false if value == ''
|
31
|
+
|
32
|
+
ast = Assertion.new(key: key, value: value, logger: logger)
|
33
|
+
%w[r p].include?(sec) ? ast_tokens_set(ast, key) : model_sec_set(ast)
|
34
|
+
|
35
|
+
model[sec] ||= {}
|
36
|
+
model[sec][key] = ast
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_model
|
40
|
+
logger.info 'Model:'
|
41
|
+
|
42
|
+
model.each do |k, v|
|
43
|
+
v.each do |i, j|
|
44
|
+
logger.info "#{k}.#{i}: #{j.value}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def ast_tokens_set(ast, key)
|
52
|
+
ast.tokens = ast.value.split(',')
|
53
|
+
ast.tokens.each_with_index { |token, i| ast.tokens[i] = "#{key}_#{token.strip}" }
|
54
|
+
end
|
55
|
+
|
56
|
+
def model_sec_set(ast)
|
57
|
+
ast.value = Util.remove_comments(Util.escape_assertion(ast.value))
|
58
|
+
end
|
59
|
+
|
60
|
+
def load_section(cfg, sec)
|
61
|
+
loop.with_index do |_, i|
|
62
|
+
break unless load_assertion(cfg, sec, "#{sec}#{get_key_suffix(i + 1)}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_sections(cfg)
|
67
|
+
SECTION_NAME_MAP.each_key { |key| load_section(cfg, key.to_s) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_assertion(cfg, sec, key)
|
71
|
+
value = cfg.get("#{SECTION_NAME_MAP[sec.to_sym]}::#{key}")
|
72
|
+
add_def(sec, key, value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_key_suffix(i)
|
76
|
+
i == 1 ? '' : i
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Casbin
|
6
|
+
module Model
|
7
|
+
class Policy
|
8
|
+
attr_reader :model, :logger
|
9
|
+
|
10
|
+
def initialize(logger: Logger.new($stdout))
|
11
|
+
@model = {}
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
# initializes the roles in RBAC.
|
16
|
+
def build_role_links(rm_map)
|
17
|
+
return unless model.key? 'g'
|
18
|
+
|
19
|
+
model['g'].each do |ptype, ast|
|
20
|
+
rm = rm_map[ptype]
|
21
|
+
ast.build_role_links(rm)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Log using info
|
26
|
+
def print_policy
|
27
|
+
logger.info 'Policy:'
|
28
|
+
|
29
|
+
%w[p g].each do |sec|
|
30
|
+
next unless model.key? sec
|
31
|
+
|
32
|
+
model[sec].each do |key, ast|
|
33
|
+
logger.info "#{key} : #{ast.value} : #{ast.policy}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# clears all current policy.
|
39
|
+
def clear_policy
|
40
|
+
%w[p g].each do |sec|
|
41
|
+
next unless model.key? sec
|
42
|
+
|
43
|
+
model[sec].each do |key, _ast|
|
44
|
+
model[sec][key].policy = []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# adds a policy rule to the model.
|
50
|
+
def add_policy(sec, ptype, rule)
|
51
|
+
return false if has_policy(sec, ptype, rule)
|
52
|
+
|
53
|
+
model[sec][ptype].policy << rule
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
# adds policy rules to the model.
|
59
|
+
def add_policies(sec, ptype, rules)
|
60
|
+
rules.each do |rule|
|
61
|
+
return false if has_policy(sec, ptype, rule)
|
62
|
+
end
|
63
|
+
|
64
|
+
model[sec][ptype].policy += rules
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
# update a policy rule from the model.
|
70
|
+
def update_policy(sec, ptype, old_rule, new_rule)
|
71
|
+
return false unless has_policy(sec, ptype, old_rule)
|
72
|
+
|
73
|
+
remove_policy(sec, ptype, old_rule) && add_policy(sec, ptype, new_rule)
|
74
|
+
end
|
75
|
+
|
76
|
+
# update policy rules from the model.
|
77
|
+
def update_policies(sec, ptype, old_rules, new_rules)
|
78
|
+
old_rules.each do |rule|
|
79
|
+
return false unless has_policy(sec, ptype, rule)
|
80
|
+
end
|
81
|
+
|
82
|
+
remove_policies(sec, ptype, old_rules) && add_policies(sec, ptype, new_rules)
|
83
|
+
end
|
84
|
+
|
85
|
+
# gets all rules in a policy.
|
86
|
+
def get_policy(sec, ptype)
|
87
|
+
model[sec][ptype].policy
|
88
|
+
end
|
89
|
+
|
90
|
+
# determines whether a model has the specified policy rule.
|
91
|
+
def has_policy(sec, ptype, rule)
|
92
|
+
model.key?(sec) && model[sec].key?(ptype) && model[sec][ptype].policy.include?(rule)
|
93
|
+
end
|
94
|
+
|
95
|
+
# removes a policy rule from the model.
|
96
|
+
def remove_policy(sec, ptype, rule)
|
97
|
+
return false unless has_policy(sec, ptype, rule)
|
98
|
+
|
99
|
+
model[sec][ptype].policy.delete(rule)
|
100
|
+
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
# removes policy rules from the model.
|
105
|
+
def remove_policies(sec, ptype, rules)
|
106
|
+
rules.each do |rule|
|
107
|
+
return false unless has_policy(sec, ptype, rule)
|
108
|
+
end
|
109
|
+
|
110
|
+
model[sec][ptype].policy.reject! { |rule| rules.include? rule }
|
111
|
+
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
# removes policy rules based on field filters from the model.
|
116
|
+
def remove_filtered_policy(sec, ptype, field_index, *field_values)
|
117
|
+
return false unless model.key?(sec) && model[sec].include?(ptype)
|
118
|
+
|
119
|
+
state = { tmp: [], res: false }
|
120
|
+
model[sec][ptype].policy.each do |rule|
|
121
|
+
state = filtered_rule(state, rule, field_values, field_index)
|
122
|
+
end
|
123
|
+
|
124
|
+
model[sec][ptype].policy = state[:tmp]
|
125
|
+
state[:res]
|
126
|
+
end
|
127
|
+
|
128
|
+
# gets all values for a field for all rules in a policy, duplicated values are removed.
|
129
|
+
def get_values_for_field_in_policy(sec, ptype, field_index)
|
130
|
+
values = []
|
131
|
+
return values unless model.keys.include?(sec)
|
132
|
+
return values unless model[sec].include?(ptype)
|
133
|
+
|
134
|
+
model[sec][ptype].policy.each do |rule|
|
135
|
+
value = rule[field_index]
|
136
|
+
values << value if values.include?(value)
|
137
|
+
end
|
138
|
+
|
139
|
+
values
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def filtered_rule(state, rule, field_values, field_index)
|
145
|
+
matched = true
|
146
|
+
|
147
|
+
field_values.each_with_index do |field_value, index|
|
148
|
+
next matched = false if field_value != '' && field_value != rule[field_index + index]
|
149
|
+
|
150
|
+
if matched
|
151
|
+
state[:res] = true
|
152
|
+
else
|
153
|
+
state[:tmp] << rule
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
state
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Casbin
|
4
|
+
module Persist
|
5
|
+
# the interface for Casbin adapters.
|
6
|
+
class Adapter
|
7
|
+
# loads all policy rules from the storage.
|
8
|
+
def load_policy(_model); end
|
9
|
+
|
10
|
+
# saves all policy rules to the storage.
|
11
|
+
def save_policy(_model); end
|
12
|
+
|
13
|
+
# adds a policy rule to the storage.
|
14
|
+
def add_policy(_sec, _ptype, _rule); end
|
15
|
+
|
16
|
+
# removes a policy rule from the storage.
|
17
|
+
def remove_policy(_sec, _ptype, _rule); end
|
18
|
+
|
19
|
+
# removes policy rules that match the filter from the storage.
|
20
|
+
# This is part of the Auto-Save feature.
|
21
|
+
def remove_filtered_policy(_sec, _ptype, _field_index, *_field_values); end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# loads a text line as a policy rule to model.
|
26
|
+
def load_policy_line(line, model)
|
27
|
+
return if line == '' || line[0] == '#'
|
28
|
+
|
29
|
+
tokens = line.split(', ')
|
30
|
+
key = tokens[0]
|
31
|
+
sec = key[0]
|
32
|
+
return unless model.model.key?(sec)
|
33
|
+
return unless model.model[sec].key?(key)
|
34
|
+
|
35
|
+
model.model[sec][key].policy << tokens[1..tokens.size]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'casbin-ruby/persist/adapter'
|
4
|
+
|
5
|
+
module Casbin
|
6
|
+
module Persist
|
7
|
+
module Adapters
|
8
|
+
# the file adapter for Casbin.
|
9
|
+
# It can load policy from file or save policy to file.
|
10
|
+
class FileAdapter < Persist::Adapter
|
11
|
+
def initialize(file_path)
|
12
|
+
super()
|
13
|
+
@file_path = file_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_policy(model)
|
17
|
+
raise 'invalid file path, file path cannot be empty' unless File.file?(file_path)
|
18
|
+
|
19
|
+
load_policy_file(model)
|
20
|
+
end
|
21
|
+
|
22
|
+
def save_policy(model)
|
23
|
+
raise 'invalid file path, file path cannot be empty' unless File.file?(file_path)
|
24
|
+
|
25
|
+
save_policy_file(model)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :file_path
|
31
|
+
|
32
|
+
def load_policy_file(model)
|
33
|
+
File.foreach(file_path) { |line| load_policy_line(line.chomp.strip, model) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def save_policy_file(model)
|
37
|
+
# 'w:UTF-8' required for Windows
|
38
|
+
File.open(file_path, 'w:UTF-8') do |file|
|
39
|
+
file.write %w[p g].map { |root_key| policy_lines(model, root_key) }.flatten.join "\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def policy_lines(model, root_key)
|
44
|
+
return [] unless model.model.key?(root_key)
|
45
|
+
|
46
|
+
model.model[root_key].map do |key, ast|
|
47
|
+
ast.policy.map { |policy_values| "#{key}, #{policy_values.join ', '}" }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'casbin-ruby/persist/adapter'
|
4
|
+
|
5
|
+
module Casbin
|
6
|
+
module Persist
|
7
|
+
# BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.
|
8
|
+
class BatchAdapter < Persist::Adapter
|
9
|
+
# AddPolicies adds policy rules to the storage.
|
10
|
+
def add_policies(_sec, _ptype, _rules); end
|
11
|
+
|
12
|
+
# LRemovePolicies removes policy rules from the storage.
|
13
|
+
def remove_policies(_sec, _ptype, _rules); end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'casbin-ruby/persist/adapter'
|
4
|
+
|
5
|
+
module Casbin
|
6
|
+
module Persist
|
7
|
+
# FilteredAdapter is the interface for Casbin adapters supporting filtered policies.
|
8
|
+
class FilteredAdapter < Persist::Adapter
|
9
|
+
# IsFiltered returns true if the loaded policy has been filtered
|
10
|
+
# Marks if the loaded policy is filtered or not
|
11
|
+
def filtered?; end
|
12
|
+
|
13
|
+
# Loads policy rules that match the filter from the storage.
|
14
|
+
def load_filtered_policy(_model, _filter); end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Casbin
|
4
|
+
module Rbac
|
5
|
+
module DefaultRoleManager
|
6
|
+
# represents the data structure for a role in RBAC.
|
7
|
+
class Role
|
8
|
+
attr_accessor :name, :roles
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
@roles = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_role(role)
|
16
|
+
return if roles.any? { |rr| rr.name == role.name }
|
17
|
+
|
18
|
+
roles << role
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_role(role)
|
22
|
+
roles.delete_if { |rr| rr.name == role.name }
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_role(role_name, hierarchy_level)
|
26
|
+
return true if role_name == name
|
27
|
+
return false if hierarchy_level.to_i <= 0
|
28
|
+
|
29
|
+
roles.each { |role| return true if role.has_role(role_name, hierarchy_level - 1) }
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_roles
|
34
|
+
roles.map(&:name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_direct_role(name)
|
38
|
+
roles.any? { |role| role.name == name }
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_string
|
42
|
+
return if roles.empty?
|
43
|
+
|
44
|
+
names = get_roles.join(', ')
|
45
|
+
if roles.size == 1
|
46
|
+
"#{name} < #{names}"
|
47
|
+
else
|
48
|
+
"#{name} < (#{names})"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|