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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +242 -0
  3. data/lib/casbin-ruby.rb +11 -0
  4. data/lib/casbin-ruby/config/config.rb +115 -0
  5. data/lib/casbin-ruby/core_enforcer.rb +356 -0
  6. data/lib/casbin-ruby/effect/allow_and_deny_effector.rb +23 -0
  7. data/lib/casbin-ruby/effect/allow_override_effector.rb +23 -0
  8. data/lib/casbin-ruby/effect/default_effector.rb +37 -0
  9. data/lib/casbin-ruby/effect/deny_override_effector.rb +23 -0
  10. data/lib/casbin-ruby/effect/effector.rb +18 -0
  11. data/lib/casbin-ruby/effect/priority_effector.rb +25 -0
  12. data/lib/casbin-ruby/enforcer.rb +189 -0
  13. data/lib/casbin-ruby/internal_enforcer.rb +73 -0
  14. data/lib/casbin-ruby/management_enforcer.rb +297 -0
  15. data/lib/casbin-ruby/model/assertion.rb +33 -0
  16. data/lib/casbin-ruby/model/function_map.rb +30 -0
  17. data/lib/casbin-ruby/model/model.rb +80 -0
  18. data/lib/casbin-ruby/model/policy.rb +161 -0
  19. data/lib/casbin-ruby/persist/adapter.rb +39 -0
  20. data/lib/casbin-ruby/persist/adapters/file_adapter.rb +53 -0
  21. data/lib/casbin-ruby/persist/batch_adapter.rb +16 -0
  22. data/lib/casbin-ruby/persist/filtered_adapter.rb +17 -0
  23. data/lib/casbin-ruby/rbac/default_role_manager/role.rb +54 -0
  24. data/lib/casbin-ruby/rbac/default_role_manager/role_manager.rb +146 -0
  25. data/lib/casbin-ruby/rbac/role_manager.rb +22 -0
  26. data/lib/casbin-ruby/synced_enforcer.rb +39 -0
  27. data/lib/casbin-ruby/util.rb +80 -0
  28. data/lib/casbin-ruby/util/builtin_operators.rb +105 -0
  29. data/lib/casbin-ruby/util/evaluator.rb +27 -0
  30. data/lib/casbin-ruby/util/thread_lock.rb +19 -0
  31. data/lib/casbin-ruby/version.rb +5 -0
  32. data/spec/casbin/config/config_spec.rb +66 -0
  33. data/spec/casbin/core_enforcer_spec.rb +473 -0
  34. data/spec/casbin/enforcer_spec.rb +302 -0
  35. data/spec/casbin/model/function_map_spec.rb +28 -0
  36. data/spec/casbin/rbac/default_role_manager/role_manager_spec.rb +131 -0
  37. data/spec/casbin/rbac/default_role_manager/role_spec.rb +84 -0
  38. data/spec/casbin/util/builtin_operators_spec.rb +205 -0
  39. data/spec/casbin/util_spec.rb +98 -0
  40. data/spec/support/model_helper.rb +9 -0
  41. 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