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.
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