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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52a08bf07cc4d7925f0e23993f1745f1cf69585ba941138c12f88ca3498705b1
|
4
|
+
data.tar.gz: 1db55f6ab3c6e853ae2f84271b08f1c67d795ea5c86afb9e7b84d94091dd9ddc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
|
data/lib/casbin-ruby.rb
ADDED
@@ -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
|