casbin-ruby 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|