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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13ea0fa54ac74dc4353530fdfb64ffc8241549538d53ec4b4290e5eeb1a296ee
4
- data.tar.gz: 7a6998a5026d11064a6d4f33d607a487c72fff0a26283ea04cada62858ca652e
3
+ metadata.gz: 52a08bf07cc4d7925f0e23993f1745f1cf69585ba941138c12f88ca3498705b1
4
+ data.tar.gz: 1db55f6ab3c6e853ae2f84271b08f1c67d795ea5c86afb9e7b84d94091dd9ddc
5
5
  SHA512:
6
- metadata.gz: 3b200f1e9e6ad4e337c5a5afc0459a7fbb7cfca07d5ce6c599de908df8702aa6b8f576f974c8087de66ac974d665438652ef80d011ba7b91fb80d7c55d749fee
7
- data.tar.gz: b1e3111991dbc4ab58fc28dc57b3ff70d4cdbf2f40d0690d20bd061b344ada0ad8471e04421034051410bf11023d2d961e20427b8f09e93f4a5664ed6b6fc6dc
6
+ metadata.gz: 3eaa9572203f449d6b653d725a05ef7c59f42bc9cd34d3ee7ef55afe63f1a933951934a63131aa0b7eb4d117b524f105561ce84e805a9617df976e35a3894ab4
7
+ data.tar.gz: dcc181227a68e920b78b705eb018a0b23a69417e8408c6014a2c22c05243ac08787a518efa73918e4266291c3a8f6b52dbd3ad2ce4ffb4b40743cdd0e0e0a2dd
data/README.md ADDED
@@ -0,0 +1,242 @@
1
+ Casbin for Ruby
2
+ ====
3
+
4
+ [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby)
5
+
6
+ **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it
7
+ at: http://casbin.org/editor/
8
+
9
+ Casbin is a powerful and efficient open-source access control library for Ruby projects. It provides support for
10
+ enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model).
11
+
12
+ ## All the languages supported by Casbin:
13
+
14
+ [![golang](https://casbin.org/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin)
15
+ ----|----|----
16
+ [Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin)
17
+ production-ready | production-ready | production-ready
18
+ [![php](https://casbin.org/img/langs/php.png)](https://github.com/php-casbin/php-casbin) | [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin-net/Casbin.NET)
19
+ [PHP-Casbin](https://github.com/php-casbin/php-casbin)|[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET)
20
+ production-ready | production-ready | production-ready
21
+ | [![c++](https://casbin.org/img/langs/cpp.png)](https://github.com/casbin/casbin-cpp) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) | [![Ruby](ruby.jpg)](https://github.com/evrone/casbin-ruby)|
22
+ [Casbin-CPP](https://github.com/casbin/casbin-cpp) | [Casbin-RS](https://github.com/casbin/casbin-rs) | [Ruby Casbin](https://github.com/evrone/casbin-ruby)
23
+ beta-test | production-ready | development
24
+
25
+ ## Table of contents
26
+
27
+ - [Supported models](#supported-models)
28
+ - [How it works?](#how-it-works)
29
+ - [Features](#features)
30
+ - [Installation](#installation)
31
+ - [Documentation](#documentation)
32
+ - [Online editor](#online-editor)
33
+ - [Tutorials](#tutorials)
34
+ - [Get started](#get-started)
35
+ - [Policy management](#policy-management)
36
+ - [Policy persistence](#policy-persistence)
37
+ - [Role manager](#role-manager)
38
+ - [Benchmarks](#benchmarks)
39
+ - [Examples](#examples)
40
+ - [Middlewares](#middlewares)
41
+ - [Our adopters](#our-adopters)
42
+
43
+ ## Supported models
44
+
45
+ 1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list)
46
+ 2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)**
47
+ 3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins.
48
+ 3. **ACL without resources**: some scenarios may target for a type of resources instead of an individual resource by using permissions like ``write-article``, ``read-log``. It doesn't control the access to a specific article or log.
49
+ 4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)**
50
+ 5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time.
51
+ 6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants.
52
+ 7. **[ABAC (Attribute-Based Access Control)](https://en.wikipedia.org/wiki/Attribute-Based_Access_Control)**: syntax sugar like ``resource.Owner`` can be used to get the attribute for a resource.
53
+ 8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``.
54
+ 9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow.
55
+ 10. **Priority**: the policy rules can be prioritized like firewall rules.
56
+
57
+ ## How it works?
58
+
59
+ In Casbin, an access control model is abstracted into a CONF file based on the **PERM metamodel (Policy, Effect, Request, Matchers)**. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules.
60
+
61
+ The most basic and simplest model in Casbin is ACL. ACL's model CONF is:
62
+
63
+ ```ini
64
+ # Request definition
65
+ [request_definition]
66
+ r = sub, obj, act
67
+
68
+ # Policy definition
69
+ [policy_definition]
70
+ p = sub, obj, act
71
+
72
+ # Policy effect
73
+ [policy_effect]
74
+ e = some(where (p.eft == allow))
75
+
76
+ # Matchers
77
+ [matchers]
78
+ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
79
+
80
+ ```
81
+
82
+ An example policy for ACL model is like:
83
+
84
+ ```
85
+ p, alice, data1, read
86
+ p, bob, data2, write
87
+ ```
88
+
89
+ It means:
90
+
91
+ - alice can read data1
92
+ - bob can write data2
93
+
94
+ We also support multi-line mode by appending '\\' in the end:
95
+
96
+ ```ini
97
+ # Matchers
98
+ [matchers]
99
+ m = r.sub == p.sub && r.obj == p.obj \
100
+ && r.act == p.act
101
+ ```
102
+
103
+ Further more, if you are using ABAC, you can try operator `in` like following in Casbin **golang** edition (jCasbin and Node-Casbin are not supported yet):
104
+
105
+ ```ini
106
+ # Matchers
107
+ [matchers]
108
+ m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')
109
+ ```
110
+
111
+ But you **SHOULD** make sure that the length of the array is **MORE** than **1**, otherwise there will cause it to panic.
112
+
113
+ For more operators, you may take a look at [govaluate](https://github.com/Knetic/govaluate)
114
+
115
+ ## Features
116
+
117
+ What Casbin does:
118
+
119
+ 1. enforce the policy in the classic ``{subject, object, action}`` form or a customized form as you defined, both allow and deny authorizations are supported.
120
+ 2. handle the storage of the access control model and its policy.
121
+ 3. manage the role-user mappings and role-role mappings (aka role hierarchy in RBAC).
122
+ 4. support built-in superuser like ``root`` or ``administrator``. A superuser can do anything without explict permissions.
123
+ 5. multiple built-in operators to support the rule matching. For example, ``keyMatch`` can map a resource key ``/foo/bar`` to the pattern ``/foo*``.
124
+
125
+ What Casbin does NOT do:
126
+
127
+ 1. authentication (aka verify ``username`` and ``password`` when a user logs in)
128
+ 2. manage the list of users or roles. I believe it's more convenient for the project itself to manage these entities. Users usually have their passwords, and Casbin is not designed as a password container. However, Casbin stores the user-role mapping for the RBAC scenario.
129
+
130
+ ## Installation
131
+
132
+ ```
133
+ gem 'casbin', github: 'evrone/casbin-ruby'
134
+ ```
135
+
136
+ ## Documentation
137
+
138
+ https://casbin.org/docs/en/overview
139
+
140
+ ## Online editor
141
+
142
+ You can also use the online editor (http://casbin.org/editor/) to write your Casbin model and policy in your web browser. It provides functionality such as ``syntax highlighting`` and ``code completion``, just like an IDE for a programming language.
143
+
144
+ ## Tutorials
145
+
146
+ https://casbin.org/docs/en/tutorials
147
+
148
+ ## Get started
149
+
150
+ 1. New a Casbin enforcer with a model file and a policy file:
151
+
152
+ ```ruby
153
+ # TODO: correct `require`
154
+ require 'casbin'
155
+ enforcer = Casbin::Enforcer.new("path/to/model.conf", "path/to/policy.csv")
156
+ ```
157
+
158
+ Note: you can also initialize an enforcer with policy in DB instead of file, see [Persistence](#persistence) section for details.
159
+
160
+ 2. Add an enforcement hook into your code right before the access happens:
161
+
162
+ ```ruby
163
+ sub = 'alice' # the user that wants to access a resource.
164
+ obj = 'data1' # the resource that is going to be accessed.
165
+ act = 'read' # the operation that the user performs on the resource.
166
+
167
+ if enforcer.enforce(sub, obj, act)
168
+ # permit alice to read data1
169
+ # do something
170
+ else
171
+ # deny the request, show an error
172
+ end
173
+ ```
174
+
175
+ 3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below:
176
+
177
+ ```ruby
178
+ roles = enforcer.get_roles_for_user('alice')
179
+ ```
180
+
181
+ See [Policy management APIs](#policy-management) for more usage.
182
+
183
+ 4. Please refer to the ``spec/support/files/examples`` files for more usage.
184
+
185
+ ## Policy management
186
+
187
+ Casbin provides two sets of APIs to manage permissions:
188
+
189
+ - [Management API](https://github.com/casbin/casbin/blob/master/management_api.go): the primitive API that provides full support for Casbin policy management. See [here](https://github.com/casbin/casbin/blob/master/management_api_test.go) for examples.
190
+ - [RBAC API](https://github.com/casbin/casbin/blob/master/rbac_api.go): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code. See [here](https://github.com/casbin/casbin/blob/master/rbac_api_test.go) for examples.
191
+
192
+ We also provide a web-based UI for model management and policy management:
193
+
194
+ ![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png)
195
+
196
+ ![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png)
197
+
198
+ ## Policy persistence
199
+
200
+ https://casbin.org/docs/en/adapters
201
+
202
+ ## Role manager
203
+
204
+ https://casbin.org/docs/en/role-managers
205
+
206
+ ## Benchmarks
207
+
208
+ https://casbin.org/docs/en/benchmark
209
+
210
+ ## Examples
211
+
212
+ Model | Model file | Policy file
213
+ ----|------|----
214
+ ACL | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv)
215
+ ACL with superuser | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv)
216
+ ACL without users | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv)
217
+ ACL without resources | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv)
218
+ RBAC | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv)
219
+ RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv)
220
+ RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv)
221
+ ABAC | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf) | N/A
222
+ RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv)
223
+ Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv)
224
+ Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv)
225
+
226
+ ## Middlewares
227
+
228
+ In process
229
+
230
+ ## Adopters
231
+
232
+ In process
233
+
234
+ ## Contributors
235
+
236
+ ## License
237
+
238
+ ## Contact
239
+
240
+ If you have any issues or feature requests, please contact us. PR is welcomed.
241
+ - https://github.com/evrone/casbin-ruby/issues
242
+
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Casbin
4
+ require 'casbin-ruby/version'
5
+ require 'casbin-ruby/enforcer'
6
+ require 'casbin-ruby/synced_enforcer'
7
+
8
+ module Persist
9
+ require 'casbin-ruby/persist/adapter'
10
+ end
11
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # represents an implementation of the ConfigInterface
4
+ module Casbin
5
+ module Config
6
+ class Config
7
+ # DEFAULT_SECTION specifies the name of a section if no name provided
8
+ DEFAULT_SECTION = 'default'
9
+ # DEFAULT_COMMENT defines what character(s) indicate a comment `#`
10
+ DEFAULT_COMMENT = '#'
11
+ # DEFAULT_COMMENT_SEM defines what alternate character(s) indicate a comment `;`
12
+ DEFAULT_COMMENT_SEM = ';'
13
+ # DEFAULT_MULTI_LINE_SEPARATOR defines what character indicates a multi-line content
14
+ DEFAULT_MULTI_LINE_SEPARATOR = '\\'
15
+
16
+ attr_reader :data
17
+
18
+ def initialize
19
+ @data = {}
20
+ end
21
+
22
+ def self.new_config(conf_name)
23
+ new.tap { |config| config.parse(conf_name) }
24
+ end
25
+
26
+ def self.new_config_from_text(text)
27
+ new.tap { |config| config.parse_from_io StringIO.new(text) }
28
+ end
29
+
30
+ def set(key, value)
31
+ raise 'key is empty' if key.to_s.size.zero?
32
+
33
+ keys = key.downcase.split('::')
34
+ if keys.size >= 2
35
+ section = keys[0]
36
+ option = keys[1]
37
+ else
38
+ section = ''
39
+ option = keys[0]
40
+ end
41
+
42
+ add_config(section, option, value)
43
+ end
44
+
45
+ # section.key or key
46
+ def get(key)
47
+ keys = key.to_s.downcase.split('::')
48
+ if keys.size >= 2
49
+ section = keys[0]
50
+ option = keys[1]
51
+ else
52
+ section = DEFAULT_SECTION
53
+ option = keys[0]
54
+ end
55
+
56
+ return '' unless data.key?(section)
57
+
58
+ data[section][option] || ''
59
+ end
60
+
61
+ def parse(conf_name)
62
+ return unless File.exist?(conf_name)
63
+
64
+ # 'r:UTF-8' required for Windows
65
+ File.open(conf_name, 'r:UTF-8') { |f| parse_from_io f }
66
+ end
67
+
68
+ def parse_from_io(io)
69
+ line_number = 0
70
+ section = ''
71
+ multi_line = ''
72
+
73
+ io.each_line do |raw|
74
+ line = raw.chomp
75
+ line_number += 1
76
+
77
+ next if line == '' || line[0] == DEFAULT_COMMENT || line[0] == DEFAULT_COMMENT_SEM
78
+ next section = line[1...-1] if line[0] == '[' && line[-1] == ']'
79
+
80
+ if line[-1] == DEFAULT_MULTI_LINE_SEPARATOR && line.strip.size > 1
81
+ part = line[0...-1].strip
82
+ multi_line = multi_line == '' ? part : "#{multi_line} #{part}"
83
+ next
84
+ end
85
+
86
+ if multi_line == ''
87
+ write(section, line, line_number)
88
+ else
89
+ multi_line += " #{line.strip}" unless line[-1] == DEFAULT_MULTI_LINE_SEPARATOR
90
+ write(section, multi_line, line_number)
91
+ multi_line = ''
92
+ end
93
+ end
94
+ end
95
+
96
+ def add_config(section, option, value)
97
+ section = DEFAULT_SECTION if section == ''
98
+ data[section] ||= {}
99
+ data[section][option] = value
100
+ end
101
+
102
+ private
103
+
104
+ def write(section, line, line_number)
105
+ option_val = line.split(' = ')
106
+ option_val[1] ||= '' # if empty value
107
+ raise "parse the content error : line #{line_number} , #{option_val[0]} = ?" unless option_val.size == 2
108
+
109
+ option = option_val[0].strip
110
+ value = option_val[1].strip
111
+ add_config(section, option, value)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'casbin-ruby/effect/default_effector'
4
+ require 'casbin-ruby/effect/effector'
5
+ require 'casbin-ruby/model/function_map'
6
+ require 'casbin-ruby/model/model'
7
+ require 'casbin-ruby/persist/adapters/file_adapter'
8
+ require 'casbin-ruby/rbac/default_role_manager/role_manager'
9
+ require 'casbin-ruby/util'
10
+ require 'casbin-ruby/util/builtin_operators'
11
+ require 'casbin-ruby/util/evaluator'
12
+
13
+ require 'logger'
14
+
15
+ module Casbin
16
+ # CoreEnforcer defines the core functionality of an enforcer.
17
+ # get_attr/set_attr methods is ported from Python as attr/attr=
18
+ class CoreEnforcer
19
+ def initialize(model = nil, adapter = nil, logger: Logger.new($stdout))
20
+ if model.is_a? String
21
+ if adapter.is_a? String
22
+ init_with_file(model, adapter, logger: logger)
23
+ else
24
+ init_with_adapter(model, adapter, logger: logger)
25
+ end
26
+ elsif adapter.is_a? String
27
+ raise 'Invalid parameters for enforcer.'
28
+ else
29
+ init_with_model_and_adapter(model, adapter, logger: logger)
30
+ end
31
+ end
32
+
33
+ attr_accessor :adapter, :auto_build_role_links, :auto_save, :effector, :enabled, :watcher, :rm_map
34
+ attr_reader :model
35
+
36
+ # initializes an enforcer with a model file and a policy file.
37
+ def init_with_file(model_path, policy_path, logger: Logger.new($stdout))
38
+ a = Persist::Adapters::FileAdapter.new(policy_path)
39
+ init_with_adapter(model_path, a, logger: logger)
40
+ end
41
+
42
+ # initializes an enforcer with a database adapter.
43
+ def init_with_adapter(model_path, adapter = nil, logger: Logger.new($stdout))
44
+ m = new_model(model_path)
45
+ init_with_model_and_adapter(m, adapter, logger: logger)
46
+
47
+ self.model_path = model_path
48
+ end
49
+
50
+ # initializes an enforcer with a model and a database adapter.
51
+ def init_with_model_and_adapter(m, adapter = nil, logger: Logger.new($stdout))
52
+ if !m.is_a?(Model::Model) || (!adapter.nil? && !adapter.is_a?(Persist::Adapter))
53
+ raise StandardError, 'Invalid parameters for enforcer.'
54
+ end
55
+
56
+ self.adapter = adapter
57
+
58
+ self.model = m
59
+ model.print_model
60
+ self.fm = Model::FunctionMap.load_function_map
61
+
62
+ init(logger: logger)
63
+
64
+ # Do not initialize the full policy when using a filtered adapter
65
+ load_policy if adapter && !filtered?
66
+ end
67
+
68
+ # creates a model.
69
+ def self.new_model(path = '', text = '', logger: Logger.new($stdout))
70
+ m = Model::Model.new logger: logger
71
+ if path.length.positive?
72
+ m.load_model(path)
73
+ else
74
+ m.load_model_from_text(text)
75
+ end
76
+
77
+ m
78
+ end
79
+
80
+ def new_model(*args)
81
+ self.class.new_model(*args)
82
+ end
83
+
84
+ # reloads the model from the model CONF file.
85
+ # Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling
86
+ # load_policy.
87
+ def load_model
88
+ self.model = new_model
89
+ model.load_model model_path
90
+ model.print_model
91
+ self.fm = Model::FunctionMap.load_function_map
92
+ end
93
+
94
+ # sets the current model.
95
+ def model=(m)
96
+ @model = m
97
+ self.fm = Model::FunctionMap.load_function_map
98
+ end
99
+
100
+ # gets the current role manager.
101
+ def role_manager
102
+ rm_map['g']
103
+ end
104
+
105
+ # sets the current role manager.
106
+ def role_manager=(rm)
107
+ rm_map['g'] = rm
108
+ end
109
+
110
+ # clears all policy.
111
+ def clear_policy
112
+ model.clear_policy
113
+ end
114
+
115
+ # reloads the policy from file/database.
116
+ def load_policy
117
+ model.clear_policy
118
+ adapter.load_policy model
119
+
120
+ init_rm_map
121
+ model.print_policy
122
+ build_role_links if auto_build_role_links
123
+ end
124
+
125
+ # reloads a filtered policy from file/database.
126
+ def load_filtered_policy(filter)
127
+ model.clear_policy
128
+
129
+ raise ArgumentError, 'filtered policies are not supported by this adapter' unless adapter.respond_to?(:filtered?)
130
+
131
+ adapter.load_filtered_policy(model, filter)
132
+ init_rm_map
133
+ model.print_policy
134
+ build_role_links if auto_build_role_links
135
+ end
136
+
137
+ # appends a filtered policy from file/database.
138
+ def load_increment_filtered_policy(filter)
139
+ raise ArgumentError, 'filtered policies are not supported by this adapter' unless adapter.respond_to?(:filtered?)
140
+
141
+ adapter.load_filtered_policy(model, filter)
142
+ model.print_policy
143
+ build_role_links if auto_build_role_links
144
+ end
145
+
146
+ # returns true if the loaded policy has been filtered.
147
+ def filtered?
148
+ adapter.respond_to?(:filtered?) && adapter.filtered?
149
+ end
150
+
151
+ def save_policy
152
+ raise 'cannot save a filtered policy' if filtered?
153
+
154
+ adapter.save_policy(model)
155
+
156
+ watcher&.update
157
+ end
158
+
159
+ alias enabled? enabled
160
+
161
+ # changes the enforcing state of Casbin,
162
+ # when Casbin is disabled, all access will be allowed by the Enforce() function.
163
+ def enable_enforce(enabled = true)
164
+ self.enabled = enabled
165
+ end
166
+
167
+ # controls whether to save a policy rule automatically to the adapter when it is added or removed.
168
+ def enable_auto_save(auto_save)
169
+ self.auto_save = auto_save
170
+ end
171
+
172
+ # controls whether to rebuild the role inheritance relations when a role is added or deleted.
173
+ def enable_auto_build_role_links(auto_build_role_links)
174
+ self.auto_build_role_links = auto_build_role_links
175
+ end
176
+
177
+ # manually rebuild the role inheritance relations.
178
+ def build_role_links
179
+ rm_map.each_value(&:clear)
180
+ model.build_role_links(rm_map)
181
+ end
182
+
183
+ # add_named_matching_func add MatchingFunc by ptype RoleManager
184
+ def add_named_matching_func(ptype, fn)
185
+ rm_map[ptype]&.add_matching_func(fn)
186
+ end
187
+
188
+ # add_named_domain_matching_func add MatchingFunc by ptype to RoleManager
189
+ def add_named_domain_matching_func(ptype, fn)
190
+ rm_map[ptype]&.add_domain_matching_func(fn)
191
+ end
192
+
193
+ # decides whether a "subject" can access a "object" with the operation "action",
194
+ # input parameters are usually: (sub, obj, act).
195
+ def enforce(*rvals)
196
+ enforce_ex(*rvals)[0]
197
+ end
198
+
199
+ # decides whether a "subject" can access a "object" with the operation "action",
200
+ # input parameters are usually: (sub, obj, act).
201
+ # return judge result with reason
202
+ def enforce_ex(*rvals)
203
+ return [false, []] unless enabled?
204
+
205
+ raise 'model is undefined' unless model.model&.key?('m')
206
+ raise 'model is undefined' unless model.model['m']&.key?('m')
207
+
208
+ r_tokens = model.model['r']['r'].tokens
209
+ p_tokens = model.model['p']['p'].tokens
210
+ raise StandardError, 'invalid request size' unless r_tokens.size == rvals.size
211
+
212
+ exp_string = model.model['m']['m'].value
213
+ has_eval = Util.has_eval(exp_string)
214
+ expression = exp_string
215
+ policy_effects = Set.new
216
+ r_parameters = load_params(r_tokens, rvals)
217
+ policy_len = model.model['p']['p'].policy.size
218
+ explain_index = -1
219
+ if policy_len.positive?
220
+ model.model['p']['p'].policy.each_with_index do |pvals, i|
221
+ raise StandardError, 'invalid policy size' unless p_tokens.size == pvals.size
222
+
223
+ p_parameters = load_params(p_tokens, pvals)
224
+ parameters = r_parameters.merge(p_parameters)
225
+
226
+ if has_eval
227
+ rule_names = Util.get_eval_value(exp_string)
228
+ rules = rule_names.map { |rule_name| Util.escape_assertion(p_parameters[rule_name]) }
229
+ expression = Util.replace_eval(exp_string, rules)
230
+ end
231
+
232
+ result = evaluate(expression, functions, parameters)
233
+ case result
234
+ when TrueClass, FalseClass
235
+ unless result
236
+ policy_effects.add(Effect::Effector::INDETERMINATE)
237
+ next
238
+ end
239
+ when Numeric
240
+ if result.zero?
241
+ policy_effects.add(Effect::Effector::INDETERMINATE)
242
+ next
243
+ end
244
+ else
245
+ raise 'matcher result should be true, false or a number'
246
+ end
247
+
248
+ if parameters.keys.include?('p_eft')
249
+ case parameters['p_eft']
250
+ when 'allow'
251
+ policy_effects.add(Effect::Effector::ALLOW)
252
+ when 'deny'
253
+ policy_effects.add(Effect::Effector::DENY)
254
+ else
255
+ policy_effects.add(Effect::Effector::INDETERMINATE)
256
+ end
257
+ else
258
+ policy_effects.add(Effect::Effector::ALLOW)
259
+ end
260
+
261
+ if effector.intermediate_effect(policy_effects) != Effect::Effector::INDETERMINATE
262
+ explain_index = i
263
+ break
264
+ end
265
+ end
266
+
267
+ else
268
+ raise 'please make sure rule exists in policy when using eval() in matcher' if has_eval
269
+
270
+ parameters = r_parameters.clone
271
+ model.model['p']['p'].tokens.each { |token| parameters[token] = '' }
272
+ result = evaluate(expression, functions, parameters)
273
+ if result
274
+ policy_effects.add(Effect::Effector::ALLOW)
275
+ else
276
+ policy_effects.add(Effect::Effector::INDETERMINATE)
277
+ end
278
+ end
279
+
280
+ final_effect = effector.final_effect(policy_effects)
281
+ result = Effect::DefaultEffector.effect_to_bool(final_effect)
282
+
283
+ # Log request.
284
+ log_request(rvals, result)
285
+
286
+ explain_rule = []
287
+ explain_rule = model.model['p']['p'].policy[explain_index] if explain_index != -1 && explain_index < policy_len
288
+ [result, explain_rule]
289
+ end
290
+
291
+ protected
292
+
293
+ attr_accessor :model_path, :fm, :auto_motify_watcher
294
+ attr_reader :logger
295
+
296
+ private
297
+
298
+ attr_accessor :matcher_map
299
+
300
+ def init(logger: Logger.new($stdout))
301
+ self.rm_map = {}
302
+ self.effector = Effect::DefaultEffector.get_effector(model.model['e']['e'].value)
303
+
304
+ self.enabled = true
305
+ self.auto_save = true
306
+ self.auto_build_role_links = true
307
+
308
+ @logger = logger
309
+
310
+ init_rm_map
311
+ end
312
+
313
+ def evaluate(expr, funcs = {}, params = {})
314
+ Util::Evaluator.eval(expr, funcs, params)
315
+ end
316
+
317
+ def load_params(tokens, values)
318
+ params = {}
319
+ tokens.each_with_index { |token, i| params[token] = values[i] }
320
+
321
+ params
322
+ end
323
+
324
+ def functions
325
+ functions = fm.get_functions
326
+
327
+ if model.model.key? 'g'
328
+ model.model['g'].each do |key, ast|
329
+ rm = ast.rm
330
+ functions[key] = Util::BuiltinOperators.generate_g_function(rm)
331
+ end
332
+ end
333
+
334
+ functions
335
+ end
336
+
337
+ def log_request(rvals, result)
338
+ req_str = "Request: #{rvals.map(&:to_s).join ', '} ---> #{result}"
339
+
340
+ if result
341
+ logger.info(req_str)
342
+ else
343
+ # leaving this in error for now, if it's very noise this can be changed to info or debug
344
+ logger.error(req_str)
345
+ end
346
+ end
347
+
348
+ def init_rm_map
349
+ return unless model.model.keys.include?('g')
350
+
351
+ model.model['g'].each_key do |ptype|
352
+ rm_map[ptype] = Rbac::DefaultRoleManager::RoleManager.new(10, logger: logger)
353
+ end
354
+ end
355
+ end
356
+ end