rabarber 1.2.2 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -2
- data/README.md +80 -24
- data/lib/rabarber/cache.rb +4 -4
- data/lib/rabarber/configuration.rb +6 -6
- data/lib/rabarber/controllers/concerns/authorization.rb +5 -5
- data/lib/rabarber/core/access.rb +28 -0
- data/lib/rabarber/core/permissions.rb +40 -0
- data/lib/rabarber/core/rule.rb +50 -0
- data/lib/rabarber/input/action.rb +26 -0
- data/lib/rabarber/input/base.rb +7 -3
- data/lib/rabarber/input/dynamic_rule.rb +26 -0
- data/lib/rabarber/input/role.rb +23 -0
- data/lib/rabarber/input/roles.rb +7 -14
- data/lib/rabarber/input/types/{booleans.rb → boolean.rb} +7 -3
- data/lib/rabarber/input/types/{procs.rb → proc.rb} +8 -4
- data/lib/rabarber/input/types/{symbols.rb → symbol.rb} +8 -4
- data/lib/rabarber/missing/base.rb +4 -4
- data/lib/rabarber/models/concerns/has_roles.rb +26 -7
- data/lib/rabarber/models/role.rb +64 -8
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +8 -6
- data/rabarber.gemspec +1 -1
- metadata +13 -12
- data/lib/rabarber/access.rb +0 -26
- data/lib/rabarber/input/actions.rb +0 -30
- data/lib/rabarber/input/dynamic_rules.rb +0 -30
- data/lib/rabarber/permissions.rb +0 -36
- data/lib/rabarber/rule.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc0447acdc988dc558859b695d24d227697258a92bb1f444944fb76bfbace526
|
4
|
+
data.tar.gz: 975f4377d5cc4fb2f28c42060012b67b3a85fea589aadb5106c33ecb943cde4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbfed2814ae750ec0b2870126818cb67c6b55c28a7529d8ab2db3364adc463768853b41ac89ba4653d014aa1024622ae261f5b2c8afcc10b99194fd916a40266
|
7
|
+
data.tar.gz: 7b77adeb8cc95acc4b104e6c39c6366b466262a8b0df0584d9e7d99d6369d96d01779a5962c7963696bccf7a941578a82774f2af23ac76d4a515bf37f7bed2ad
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 1.3.1
|
2
|
+
|
3
|
+
- Add `Rabarber::Role.assignees_for` method
|
4
|
+
- Fix inconsistent behavior where passing `nil` as a role name to role management methods would raise an `ActiveRecord` error instead of `Rabarber` error
|
5
|
+
- Various minor code improvements
|
6
|
+
|
7
|
+
## 1.3.0
|
8
|
+
|
9
|
+
- Add methods to directly add, rename, and remove roles
|
10
|
+
- Modify `Rabarber::HasRoles#assign_roles` and `Rabarber::HasRoles#revoke_roles` methods to return the list of roles assigned to the user
|
11
|
+
- Minor performance improvements
|
12
|
+
|
1
13
|
## 1.2.2
|
2
14
|
|
3
15
|
- Refactor to improve readability and maintainability
|
@@ -8,6 +20,7 @@
|
|
8
20
|
- Cache roles to avoid unnecessary database queries
|
9
21
|
- Introduce `cache_enabled` configuration option allowing to enable or disable role caching
|
10
22
|
- Enhance the migration generator so that it can receive the table name of the model representing users in the application as an argument
|
23
|
+
- Fix an issue where an error would be raised if the user is not authenticated
|
11
24
|
- Various minor improvements
|
12
25
|
|
13
26
|
## 1.2.0
|
@@ -36,7 +49,7 @@
|
|
36
49
|
## 1.0.2
|
37
50
|
|
38
51
|
- Various enhancements for gem development and release
|
39
|
-
- Modify `HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
|
52
|
+
- Modify `Rabarber::HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
|
40
53
|
|
41
54
|
## 1.0.1
|
42
55
|
|
@@ -55,7 +68,7 @@
|
|
55
68
|
|
56
69
|
## 0.1.4
|
57
70
|
|
58
|
-
- Remove `HasRoles#role?` method as unnecessary
|
71
|
+
- Remove `Rabarber::HasRoles#role?` method as unnecessary
|
59
72
|
|
60
73
|
## 0.1.3
|
61
74
|
|
data/README.md
CHANGED
@@ -3,9 +3,7 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/rabarber.svg)](http://badge.fury.io/rb/rabarber)
|
4
4
|
[![Github Actions badge](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
|
5
5
|
|
6
|
-
Rabarber is a role-based authorization library for Ruby on Rails, designed primarily for use in the web layer (specifically controllers and views) but not limited to that. It provides tools for managing user roles and defining authorization rules
|
7
|
-
|
8
|
-
Unlike some other libraries, Rabarber does not handle data scoping. Instead, it focuses on providing a lightweight and flexible solution for role-based access control, allowing developers to implement data scoping according to their specific business rules directly within their application's code.
|
6
|
+
Rabarber is a role-based authorization library for Ruby on Rails, designed primarily for use in the application's web layer (specifically controllers and views) but not limited to that. It provides tools for managing user roles and defining authorization rules and mainly focuses on answering the question: 'Who has access to which endpoints?'.
|
9
7
|
|
10
8
|
---
|
11
9
|
|
@@ -53,8 +51,6 @@ Next, generate a migration to create tables for storing roles in the database. M
|
|
53
51
|
rails g rabarber:roles users
|
54
52
|
```
|
55
53
|
|
56
|
-
This will create a migration file in `db/migrate` directory.
|
57
|
-
|
58
54
|
Finally, run the migration to apply the changes to the database:
|
59
55
|
|
60
56
|
```
|
@@ -63,30 +59,30 @@ rails db:migrate
|
|
63
59
|
|
64
60
|
## Configuration
|
65
61
|
|
66
|
-
Rabarber can be configured by using `.configure` method in an initializer:
|
62
|
+
If specific customization is required, Rabarber can be configured by using `.configure` method in an initializer:
|
67
63
|
|
68
64
|
```rb
|
69
65
|
Rabarber.configure do |config|
|
70
|
-
config.cache_enabled =
|
71
|
-
config.current_user_method = :
|
72
|
-
config.must_have_roles =
|
66
|
+
config.cache_enabled = true
|
67
|
+
config.current_user_method = :current_user
|
68
|
+
config.must_have_roles = false
|
73
69
|
config.when_actions_missing = -> (missing_actions, context) { ... }
|
74
70
|
config.when_roles_missing = -> (missing_roles, context) { ... }
|
75
71
|
config.when_unauthorized = -> (controller) { ... }
|
76
72
|
end
|
77
73
|
```
|
78
74
|
|
79
|
-
- `cache_enabled` must be a boolean determining whether roles are cached.
|
75
|
+
- `cache_enabled` must be a boolean determining whether roles are cached. _Roles are cached by default to avoid unnecessary database queries._ If you want to disable caching, set this option to `false`. If caching is enabled and you need to clear the cache, use `Rabarber::Cache.clear` method.
|
80
76
|
|
81
|
-
- `current_user_method` must be a symbol representing the method that returns the currently authenticated user.
|
77
|
+
- `current_user_method` must be a symbol representing the method that returns the currently authenticated user. _The default value is `:current_user`._
|
82
78
|
|
83
|
-
- `must_have_roles` must be a boolean determining whether a user with no roles can access endpoints permitted to everyone.
|
79
|
+
- `must_have_roles` must be a boolean determining whether a user with no roles can access endpoints permitted to everyone. _The default value is `false` (allowing users without roles to access endpoints permitted to everyone)._
|
84
80
|
|
85
|
-
- `when_actions_missing` must be a proc where you can define the behaviour when the actions specified in `grant_access` method cannot be found in the controller (`missing_actions` is an array of
|
81
|
+
- `when_actions_missing` must be a proc where you can define the behaviour when the actions specified in `grant_access` method cannot be found in the controller (`missing_actions` is an array of symbols e.g., `[:index]`, `context` is a hash that looks like this: `{ controller: "InvoicesController" }`). This check is performed on every request and when the application is initialized if `eager_load` configuration is enabled in Rails. _By default, an error is raised when actions are missing._
|
86
82
|
|
87
|
-
- `when_roles_missing` must be a proc where you can define the behaviour when the roles specified in `grant_access` method cannot be found in the database (`missing_roles` is an array of
|
83
|
+
- `when_roles_missing` must be a proc where you can define the behaviour when the roles specified in `grant_access` method cannot be found in the database (`missing_roles` is an array of symbols e.g., `[:admin]`, `context` is a hash that looks like this: `{ controller: "InvoicesController", action: "index" }`). This check is performed on every request and when the application is initialized if `eager_load` configuration is enabled in Rails. _By default, only a warning is logged when roles are missing._
|
88
84
|
|
89
|
-
- `when_unauthorized` must be a proc where you can define the behaviour when access is not authorized (`controller` is an instance of the controller where the code is executed).
|
85
|
+
- `when_unauthorized` must be a proc where you can define the behaviour when access is not authorized (`controller` is an instance of the controller where the code is executed). _By default, the user is redirected back if the request format is HTML; otherwise, a 401 Unauthorized response is sent._
|
90
86
|
|
91
87
|
## Roles
|
92
88
|
|
@@ -103,7 +99,7 @@ This adds the following methods:
|
|
103
99
|
|
104
100
|
**`#assign_roles`**
|
105
101
|
|
106
|
-
To assign roles
|
102
|
+
To assign roles, use:
|
107
103
|
|
108
104
|
```rb
|
109
105
|
user.assign_roles(:accountant, :marketer)
|
@@ -112,6 +108,7 @@ By default, `#assign_roles` method will automatically create any roles that don'
|
|
112
108
|
```rb
|
113
109
|
user.assign_roles(:accountant, :marketer, create_new: false)
|
114
110
|
```
|
111
|
+
The method returns an array of roles assigned to the user.
|
115
112
|
|
116
113
|
**`#revoke_roles`**
|
117
114
|
|
@@ -122,6 +119,8 @@ user.revoke_roles(:accountant, :marketer)
|
|
122
119
|
```
|
123
120
|
If any of the specified roles doesn't exist or the user doesn't have the role you want to revoke, it will be ignored.
|
124
121
|
|
122
|
+
The method returns an array of roles assigned to the user.
|
123
|
+
|
125
124
|
**`#has_role?`**
|
126
125
|
|
127
126
|
To check whether the user has a role, use:
|
@@ -140,15 +139,68 @@ To view all the roles assigned to the user, use:
|
|
140
139
|
user.roles
|
141
140
|
```
|
142
141
|
|
142
|
+
---
|
143
|
+
|
144
|
+
To manipulate roles directly, you can use `Rabarber::Role` methods:
|
145
|
+
|
146
|
+
**`.add`**
|
147
|
+
|
148
|
+
To add a new role, use:
|
149
|
+
|
150
|
+
```rb
|
151
|
+
Rabarber::Role.add(:admin)
|
152
|
+
```
|
153
|
+
|
154
|
+
This will create a new role with the specified name and return `true`. If the role already exists, it will return `false`.
|
155
|
+
|
156
|
+
**`.rename`**
|
157
|
+
|
158
|
+
To rename a role, use:
|
159
|
+
|
160
|
+
```rb
|
161
|
+
Rabarber::Role.rename(:admin, :administrator)
|
162
|
+
```
|
163
|
+
The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If a role with the new name already exists, it will return `false`.
|
164
|
+
|
165
|
+
The method won't rename the role and will return `false` if it is assigned to any user. To force the rename, use the method with `force: true` argument:
|
166
|
+
```rb
|
167
|
+
Rabarber::Role.rename(:admin, :administrator, force: true)
|
168
|
+
```
|
169
|
+
|
170
|
+
**`.remove`**
|
171
|
+
|
172
|
+
To remove a role, use:
|
173
|
+
|
174
|
+
```rb
|
175
|
+
Rabarber::Role.remove(:admin)
|
176
|
+
```
|
177
|
+
|
178
|
+
This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
|
179
|
+
|
180
|
+
The method won't remove the role and will return `false` if it is assigned to any user. To force the removal, use the method with `force: true` argument:
|
181
|
+
```rb
|
182
|
+
Rabarber::Role.remove(:admin, force: true)
|
183
|
+
```
|
184
|
+
|
185
|
+
**`.names`**
|
186
|
+
|
143
187
|
If you need to list all the role names available in your application, use:
|
144
188
|
|
145
189
|
```rb
|
146
190
|
Rabarber::Role.names
|
147
191
|
```
|
148
192
|
|
193
|
+
**`.assignees_for`**
|
194
|
+
|
195
|
+
To get all the users to whom the role is assigned, use:
|
196
|
+
|
197
|
+
```rb
|
198
|
+
Rabarber::Role.assignees_for(:admin)
|
199
|
+
```
|
200
|
+
|
149
201
|
## Authorization Rules
|
150
202
|
|
151
|
-
Include `Rabarber::Authorization` module into the controller that needs authorization rules to be applied
|
203
|
+
Include `Rabarber::Authorization` module into the controller that needs authorization rules to be applied. Typically, it is `ApplicationController`, but it can be any controller of your choice.
|
152
204
|
|
153
205
|
```rb
|
154
206
|
class ApplicationController < ActionController::Base
|
@@ -216,9 +268,9 @@ class InvoicesController < ApplicationController
|
|
216
268
|
end
|
217
269
|
```
|
218
270
|
|
219
|
-
This allows everyone to access `OrdersController` and its children and `index` action in `InvoicesController`. This
|
271
|
+
This allows everyone to access `OrdersController` and its children and also `index` action in `InvoicesController`. This extends to scenarios where there is no user present, i.e. when the method responsible for returning the currently authenticated user in your application returns `nil`.
|
220
272
|
|
221
|
-
|
273
|
+
_Be aware that if the user is not authenticated (the method responsible for returning the currently authenticated user in your application returns `nil`), Rabarber will treat this situation as if the user with no roles assigned was authenticated._
|
222
274
|
|
223
275
|
If you've set `must_have_roles` setting to `true`, then, only the users with at least one role can have access. This setting can be useful if your requirements are such that users without roles are not allowed to access anything.
|
224
276
|
|
@@ -253,7 +305,7 @@ class InvoicesController < ApplicationController
|
|
253
305
|
end
|
254
306
|
end
|
255
307
|
```
|
256
|
-
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol
|
308
|
+
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol, in which case the method with the same name will be called. Alternatively, it can be a proc, which will be executed within the context of the controller's instance.
|
257
309
|
|
258
310
|
Rules defined in child classes don't override parent rules but rather add to them:
|
259
311
|
```rb
|
@@ -271,7 +323,7 @@ This means that `Crm::InvoicesController` is still accessible to `admin` but is
|
|
271
323
|
|
272
324
|
## View Helpers
|
273
325
|
|
274
|
-
Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. To use them, simply include `Rabarber::Helpers` in the desired helper
|
326
|
+
Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. To use them, simply include `Rabarber::Helpers` in the desired helper. Usually it is `ApplicationHelper`, but it can be any helper of your choice.
|
275
327
|
|
276
328
|
```rb
|
277
329
|
module ApplicationHelper
|
@@ -296,14 +348,18 @@ The usage is straightforward:
|
|
296
348
|
|
297
349
|
## Problems?
|
298
350
|
|
299
|
-
|
351
|
+
Facing a problem or want to suggest an enhancement?
|
352
|
+
|
353
|
+
- **Open a Discussion**: If you have a question, experience difficulties using the gem, or have an improvement suggestion, feel free to use the Discussions section.
|
354
|
+
|
355
|
+
Encountered a bug?
|
300
356
|
|
301
|
-
- **Create an Issue**: If you've identified a
|
357
|
+
- **Create an Issue**: If you've identified a bug, please create an issue. Be sure to provide detailed information about the problem, including the steps to reproduce it.
|
302
358
|
- **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
|
303
359
|
|
304
360
|
## Contributing
|
305
361
|
|
306
|
-
|
362
|
+
Before opening an issue or creating a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
|
307
363
|
|
308
364
|
## License
|
309
365
|
|
data/lib/rabarber/cache.rb
CHANGED
@@ -10,16 +10,16 @@ module Rabarber
|
|
10
10
|
enabled? ? Rails.cache.fetch(key, options, &block) : yield
|
11
11
|
end
|
12
12
|
|
13
|
-
def delete(
|
14
|
-
Rails.cache.
|
13
|
+
def delete(*keys)
|
14
|
+
Rails.cache.delete_multi(keys) if enabled?
|
15
15
|
end
|
16
16
|
|
17
17
|
def enabled?
|
18
18
|
Rabarber::Configuration.instance.cache_enabled
|
19
19
|
end
|
20
20
|
|
21
|
-
def key_for(
|
22
|
-
"rabarber:roles_#{
|
21
|
+
def key_for(id)
|
22
|
+
"rabarber:roles_#{id}"
|
23
23
|
end
|
24
24
|
|
25
25
|
def clear
|
@@ -19,37 +19,37 @@ module Rabarber
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def cache_enabled=(value)
|
22
|
-
@cache_enabled = Rabarber::Input::Types::
|
22
|
+
@cache_enabled = Rabarber::Input::Types::Boolean.new(
|
23
23
|
value, Rabarber::ConfigurationError, "Configuration 'cache_enabled' must be a Boolean"
|
24
24
|
).process
|
25
25
|
end
|
26
26
|
|
27
27
|
def current_user_method=(method_name)
|
28
|
-
@current_user_method = Rabarber::Input::Types::
|
28
|
+
@current_user_method = Rabarber::Input::Types::Symbol.new(
|
29
29
|
method_name, Rabarber::ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
|
30
30
|
).process
|
31
31
|
end
|
32
32
|
|
33
33
|
def must_have_roles=(value)
|
34
|
-
@must_have_roles = Rabarber::Input::Types::
|
34
|
+
@must_have_roles = Rabarber::Input::Types::Boolean.new(
|
35
35
|
value, Rabarber::ConfigurationError, "Configuration 'must_have_roles' must be a Boolean"
|
36
36
|
).process
|
37
37
|
end
|
38
38
|
|
39
39
|
def when_actions_missing=(callable)
|
40
|
-
@when_actions_missing = Rabarber::Input::Types::
|
40
|
+
@when_actions_missing = Rabarber::Input::Types::Proc.new(
|
41
41
|
callable, Rabarber::ConfigurationError, "Configuration 'when_actions_missing' must be a Proc"
|
42
42
|
).process
|
43
43
|
end
|
44
44
|
|
45
45
|
def when_roles_missing=(callable)
|
46
|
-
@when_roles_missing = Rabarber::Input::Types::
|
46
|
+
@when_roles_missing = Rabarber::Input::Types::Proc.new(
|
47
47
|
callable, Rabarber::ConfigurationError, "Configuration 'when_roles_missing' must be a Proc"
|
48
48
|
).process
|
49
49
|
end
|
50
50
|
|
51
51
|
def when_unauthorized=(callable)
|
52
|
-
@when_unauthorized = Rabarber::Input::Types::
|
52
|
+
@when_unauthorized = Rabarber::Input::Types::Proc.new(
|
53
53
|
callable, Rabarber::ConfigurationError, "Configuration 'when_unauthorized' must be a Proc"
|
54
54
|
).process
|
55
55
|
end
|
@@ -12,12 +12,12 @@ module Rabarber
|
|
12
12
|
def grant_access(action: nil, roles: nil, if: nil, unless: nil)
|
13
13
|
dynamic_rule, negated_dynamic_rule = binding.local_variable_get(:if), binding.local_variable_get(:unless)
|
14
14
|
|
15
|
-
Rabarber::Permissions.add(
|
15
|
+
Rabarber::Core::Permissions.add(
|
16
16
|
self,
|
17
|
-
Rabarber::Input::
|
17
|
+
Rabarber::Input::Action.new(action).process,
|
18
18
|
Rabarber::Input::Roles.new(roles).process,
|
19
|
-
Rabarber::Input::
|
20
|
-
Rabarber::Input::
|
19
|
+
Rabarber::Input::DynamicRule.new(dynamic_rule).process,
|
20
|
+
Rabarber::Input::DynamicRule.new(negated_dynamic_rule).process
|
21
21
|
)
|
22
22
|
end
|
23
23
|
end
|
@@ -28,7 +28,7 @@ module Rabarber
|
|
28
28
|
Rabarber::Missing::Actions.new(self.class).handle
|
29
29
|
Rabarber::Missing::Roles.new(self.class).handle
|
30
30
|
|
31
|
-
return if Rabarber::Permissions.access_granted?(rabarber_roles, self.class, action_name.to_sym, self)
|
31
|
+
return if Rabarber::Core::Permissions.access_granted?(rabarber_roles, self.class, action_name.to_sym, self)
|
32
32
|
|
33
33
|
Rabarber::Configuration.instance.when_unauthorized.call(self)
|
34
34
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Core
|
5
|
+
module Access
|
6
|
+
def access_granted?(roles, controller, action, dynamic_rule_receiver)
|
7
|
+
controller_accessible?(roles, controller, dynamic_rule_receiver) ||
|
8
|
+
action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
9
|
+
end
|
10
|
+
|
11
|
+
def controller_accessible?(roles, controller, dynamic_rule_receiver)
|
12
|
+
accessible_controllers(roles, dynamic_rule_receiver).any? do |accessible_controller|
|
13
|
+
controller <= accessible_controller
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
18
|
+
action_rules[controller].any? { |rule| rule.verify_access(roles, dynamic_rule_receiver, action) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def accessible_controllers(roles, dynamic_rule_receiver)
|
24
|
+
controller_rules.select { |_, rule| rule.verify_access(roles, dynamic_rule_receiver) }.keys
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "access"
|
4
|
+
require_relative "rule"
|
5
|
+
|
6
|
+
module Rabarber
|
7
|
+
module Core
|
8
|
+
class Permissions
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
extend Access
|
12
|
+
|
13
|
+
attr_reader :storage
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
|
21
|
+
rule = Rabarber::Core::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
22
|
+
|
23
|
+
if action
|
24
|
+
instance.storage[:action_rules][controller] += [rule]
|
25
|
+
else
|
26
|
+
instance.storage[:controller_rules][controller] = rule
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def controller_rules
|
31
|
+
instance.storage[:controller_rules]
|
32
|
+
end
|
33
|
+
|
34
|
+
def action_rules
|
35
|
+
instance.storage[:action_rules]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Core
|
5
|
+
class Rule
|
6
|
+
attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
|
7
|
+
|
8
|
+
def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
|
9
|
+
@action = action
|
10
|
+
@roles = Array(roles)
|
11
|
+
@dynamic_rule = dynamic_rule
|
12
|
+
@negated_dynamic_rule = negated_dynamic_rule
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify_access(user_roles, dynamic_rule_receiver, action_name = nil)
|
16
|
+
action_accessible?(action_name) && roles_permitted?(user_roles) && dynamic_rule_followed?(dynamic_rule_receiver)
|
17
|
+
end
|
18
|
+
|
19
|
+
def action_accessible?(action_name)
|
20
|
+
action_name.nil? || action_name == action
|
21
|
+
end
|
22
|
+
|
23
|
+
def roles_permitted?(user_roles)
|
24
|
+
return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
|
25
|
+
|
26
|
+
roles.empty? || (roles & user_roles).any?
|
27
|
+
end
|
28
|
+
|
29
|
+
def dynamic_rule_followed?(dynamic_rule_receiver)
|
30
|
+
!!(execute_dynamic_rule(dynamic_rule_receiver, false) && execute_dynamic_rule(dynamic_rule_receiver, true))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def execute_dynamic_rule(dynamic_rule_receiver, is_negated)
|
36
|
+
rule = is_negated ? negated_dynamic_rule : dynamic_rule
|
37
|
+
|
38
|
+
return true if rule.nil?
|
39
|
+
|
40
|
+
result = if rule.is_a?(Proc)
|
41
|
+
dynamic_rule_receiver.instance_exec(&rule)
|
42
|
+
else
|
43
|
+
dynamic_rule_receiver.send(rule)
|
44
|
+
end
|
45
|
+
|
46
|
+
is_negated ? !result : result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class Action < Rabarber::Input::Base
|
6
|
+
def valid?
|
7
|
+
Rabarber::Input::Types::Symbol.new(value).valid? || value.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def processed_value
|
13
|
+
case value
|
14
|
+
when String, Symbol
|
15
|
+
value.to_sym
|
16
|
+
when nil
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_error_message
|
22
|
+
"Action name must be a Symbol or a String"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/rabarber/input/base.rb
CHANGED
@@ -5,7 +5,7 @@ module Rabarber
|
|
5
5
|
class Base
|
6
6
|
attr_reader :value, :error_type, :error_message
|
7
7
|
|
8
|
-
def initialize(value, error_type, error_message)
|
8
|
+
def initialize(value, error_type = Rabarber::InvalidArgumentError, error_message = default_error_message)
|
9
9
|
@value = value
|
10
10
|
@error_type = error_type
|
11
11
|
@error_message = error_message
|
@@ -15,16 +15,20 @@ module Rabarber
|
|
15
15
|
valid? ? processed_value : raise_error
|
16
16
|
end
|
17
17
|
|
18
|
-
private
|
19
|
-
|
20
18
|
def valid?
|
21
19
|
raise NotImplementedError
|
22
20
|
end
|
23
21
|
|
22
|
+
private
|
23
|
+
|
24
24
|
def processed_value
|
25
25
|
raise NotImplementedError
|
26
26
|
end
|
27
27
|
|
28
|
+
def default_error_message
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
28
32
|
def raise_error
|
29
33
|
raise error_type, error_message
|
30
34
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class DynamicRule < Rabarber::Input::Base
|
6
|
+
def valid?
|
7
|
+
Rabarber::Input::Types::Symbol.new(value).valid? || Rabarber::Input::Types::Proc.new(value).valid? || value.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def processed_value
|
13
|
+
case value
|
14
|
+
when String, Symbol
|
15
|
+
value.to_sym
|
16
|
+
when Proc, nil
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_error_message
|
22
|
+
"Dynamic rule must be a Symbol, a String, or a Proc"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class Role < Rabarber::Input::Base
|
6
|
+
REGEX = /\A[a-z0-9_]+\z/
|
7
|
+
|
8
|
+
def valid?
|
9
|
+
Rabarber::Input::Types::Symbol.new(value).valid? && value.to_s.match?(REGEX)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def processed_value
|
15
|
+
value.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_error_message
|
19
|
+
"Role name must be a Symbol or a String and may only contain lowercase letters, numbers and underscores"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rabarber/input/roles.rb
CHANGED
@@ -3,30 +3,23 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Input
|
5
5
|
class Roles < Rabarber::Input::Base
|
6
|
-
REGEX = /\A[a-z0-9_]+\z/
|
7
|
-
|
8
|
-
def initialize(
|
9
|
-
value,
|
10
|
-
error_type = Rabarber::InvalidArgumentError,
|
11
|
-
error_message =
|
12
|
-
"Role names must be Symbols or Strings and may only contain lowercase letters, numbers and underscores"
|
13
|
-
)
|
14
|
-
super
|
15
|
-
end
|
16
|
-
|
17
6
|
def value
|
18
7
|
Array(super)
|
19
8
|
end
|
20
9
|
|
21
|
-
private
|
22
|
-
|
23
10
|
def valid?
|
24
|
-
value.all? { |role_name|
|
11
|
+
value.all? { |role_name| Rabarber::Input::Role.new(role_name).valid? }
|
25
12
|
end
|
26
13
|
|
14
|
+
private
|
15
|
+
|
27
16
|
def processed_value
|
28
17
|
value.map(&:to_sym)
|
29
18
|
end
|
19
|
+
|
20
|
+
def default_error_message
|
21
|
+
"Role names must be Symbols or Strings and may only contain lowercase letters, numbers and underscores"
|
22
|
+
end
|
30
23
|
end
|
31
24
|
end
|
32
25
|
end
|
@@ -3,16 +3,20 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Input
|
5
5
|
module Types
|
6
|
-
class
|
7
|
-
private
|
8
|
-
|
6
|
+
class Boolean < Rabarber::Input::Base
|
9
7
|
def valid?
|
10
8
|
[true, false].include?(value)
|
11
9
|
end
|
12
10
|
|
11
|
+
private
|
12
|
+
|
13
13
|
def processed_value
|
14
14
|
value
|
15
15
|
end
|
16
|
+
|
17
|
+
def default_error_message
|
18
|
+
"Value must be a Boolean"
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -3,16 +3,20 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Input
|
5
5
|
module Types
|
6
|
-
class
|
7
|
-
private
|
8
|
-
|
6
|
+
class Proc < Rabarber::Input::Base
|
9
7
|
def valid?
|
10
|
-
value.is_a?(Proc)
|
8
|
+
value.is_a?(::Proc)
|
11
9
|
end
|
12
10
|
|
11
|
+
private
|
12
|
+
|
13
13
|
def processed_value
|
14
14
|
value
|
15
15
|
end
|
16
|
+
|
17
|
+
def default_error_message
|
18
|
+
"Value must be a Proc"
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -3,16 +3,20 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Input
|
5
5
|
module Types
|
6
|
-
class
|
7
|
-
private
|
8
|
-
|
6
|
+
class Symbol < Rabarber::Input::Base
|
9
7
|
def valid?
|
10
|
-
(value.is_a?(Symbol) || value.is_a?(String)) && value.present?
|
8
|
+
(value.is_a?(::Symbol) || value.is_a?(String)) && value.present?
|
11
9
|
end
|
12
10
|
|
11
|
+
private
|
12
|
+
|
13
13
|
def processed_value
|
14
14
|
value.to_sym
|
15
15
|
end
|
16
|
+
|
17
|
+
def default_error_message
|
18
|
+
"Value must be a Symbol or a String"
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -41,17 +41,17 @@ module Rabarber
|
|
41
41
|
|
42
42
|
def controller_rules
|
43
43
|
if controller
|
44
|
-
Rabarber::Permissions.controller_rules.slice(controller)
|
44
|
+
Rabarber::Core::Permissions.controller_rules.slice(controller)
|
45
45
|
else
|
46
|
-
Rabarber::Permissions.controller_rules
|
46
|
+
Rabarber::Core::Permissions.controller_rules
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
50
|
def action_rules
|
51
51
|
if controller
|
52
|
-
Rabarber::Permissions.action_rules.slice(controller)
|
52
|
+
Rabarber::Core::Permissions.action_rules.slice(controller)
|
53
53
|
else
|
54
|
-
Rabarber::Permissions.action_rules
|
54
|
+
Rabarber::Core::Permissions.action_rules
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -17,7 +17,7 @@ module Rabarber
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def roles
|
20
|
-
Rabarber::Cache.fetch(Rabarber::Cache.key_for(
|
20
|
+
Rabarber::Cache.fetch(Rabarber::Cache.key_for(roleable_id), expires_in: 1.hour, race_condition_ttl: 5.seconds) do
|
21
21
|
rabarber_roles.names
|
22
22
|
end
|
23
23
|
end
|
@@ -31,17 +31,32 @@ module Rabarber
|
|
31
31
|
|
32
32
|
create_new_roles(roles_to_assign) if create_new
|
33
33
|
|
34
|
-
|
34
|
+
new_roles = Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
|
35
35
|
|
36
|
-
|
36
|
+
if new_roles.any?
|
37
|
+
delete_roleable_cache
|
38
|
+
rabarber_roles << new_roles
|
39
|
+
end
|
40
|
+
|
41
|
+
roles
|
37
42
|
end
|
38
43
|
|
39
44
|
def revoke_roles(*role_names)
|
40
|
-
|
45
|
+
new_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
|
46
|
+
|
47
|
+
if rabarber_roles != new_roles
|
48
|
+
delete_roleable_cache
|
49
|
+
self.rabarber_roles = new_roles
|
50
|
+
end
|
41
51
|
|
42
|
-
|
52
|
+
roles
|
43
53
|
end
|
44
54
|
|
55
|
+
def roleable_class
|
56
|
+
@@included.constantize
|
57
|
+
end
|
58
|
+
module_function :roleable_class
|
59
|
+
|
45
60
|
private
|
46
61
|
|
47
62
|
def create_new_roles(role_names)
|
@@ -53,8 +68,12 @@ module Rabarber
|
|
53
68
|
Rabarber::Input::Roles.new(role_names).process
|
54
69
|
end
|
55
70
|
|
56
|
-
def
|
57
|
-
Rabarber::Cache.delete(Rabarber::Cache.key_for(
|
71
|
+
def delete_roleable_cache
|
72
|
+
Rabarber::Cache.delete(Rabarber::Cache.key_for(roleable_id))
|
73
|
+
end
|
74
|
+
|
75
|
+
def roleable_id
|
76
|
+
public_send(self.class.primary_key)
|
58
77
|
end
|
59
78
|
end
|
60
79
|
end
|
data/lib/rabarber/models/role.rb
CHANGED
@@ -4,18 +4,74 @@ module Rabarber
|
|
4
4
|
class Role < ActiveRecord::Base
|
5
5
|
self.table_name = "rabarber_roles"
|
6
6
|
|
7
|
-
validates :name, presence: true, uniqueness: true, format: { with: Rabarber::Input::
|
7
|
+
validates :name, presence: true, uniqueness: true, format: { with: Rabarber::Input::Role::REGEX }, strict: true
|
8
8
|
|
9
|
-
|
9
|
+
has_and_belongs_to_many :roleables, join_table: "rabarber_roles_roleables"
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class << self
|
12
|
+
def names
|
13
|
+
pluck(:name).map(&:to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(name)
|
17
|
+
name = process_role_name(name)
|
18
|
+
|
19
|
+
return false if exists?(name: name)
|
20
|
+
|
21
|
+
delete_roles_cache
|
22
|
+
|
23
|
+
!!create!(name: name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rename(old_name, new_name, force: false)
|
27
|
+
role = find_by(name: process_role_name(old_name))
|
28
|
+
name = process_role_name(new_name)
|
29
|
+
|
30
|
+
return false if !role || exists?(name: name) || assigned_to_roleables(role).any? && !force
|
31
|
+
|
32
|
+
delete_roles_cache
|
33
|
+
delete_roleables_cache(role)
|
34
|
+
|
35
|
+
role.update!(name: name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove(name, force: false)
|
39
|
+
role = find_by(name: process_role_name(name))
|
40
|
+
|
41
|
+
return false if !role || assigned_to_roleables(role).any? && !force
|
42
|
+
|
43
|
+
delete_roles_cache
|
44
|
+
delete_roleables_cache(role)
|
45
|
+
|
46
|
+
!!role.destroy!
|
47
|
+
end
|
48
|
+
|
49
|
+
def assignees_for(name)
|
50
|
+
Rabarber::HasRoles.roleable_class.joins(:rabarber_roles).where(
|
51
|
+
rabarber_roles: { name: Rabarber::Input::Role.new(name).process }
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def delete_roles_cache
|
58
|
+
Rabarber::Cache.delete(Rabarber::Cache::ALL_ROLES_KEY)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_roleables_cache(role)
|
62
|
+
keys = assigned_to_roleables(role).map { |roleable_id| Rabarber::Cache.key_for(roleable_id) }
|
63
|
+
Rabarber::Cache.delete(*keys) if keys.any?
|
64
|
+
end
|
14
65
|
|
15
|
-
|
66
|
+
def assigned_to_roleables(role)
|
67
|
+
ActiveRecord::Base.connection.select_values(
|
68
|
+
"SELECT roleable_id FROM rabarber_roles_roleables WHERE role_id = #{role.id}"
|
69
|
+
)
|
70
|
+
end
|
16
71
|
|
17
|
-
|
18
|
-
|
72
|
+
def process_role_name(name)
|
73
|
+
Rabarber::Input::Role.new(name).process
|
74
|
+
end
|
19
75
|
end
|
20
76
|
end
|
21
77
|
end
|
data/lib/rabarber/version.rb
CHANGED
data/lib/rabarber.rb
CHANGED
@@ -8,12 +8,13 @@ require "active_record"
|
|
8
8
|
require "active_support"
|
9
9
|
|
10
10
|
require_relative "rabarber/input/base"
|
11
|
-
require_relative "rabarber/input/
|
12
|
-
require_relative "rabarber/input/
|
11
|
+
require_relative "rabarber/input/action"
|
12
|
+
require_relative "rabarber/input/dynamic_rule"
|
13
|
+
require_relative "rabarber/input/role"
|
13
14
|
require_relative "rabarber/input/roles"
|
14
|
-
require_relative "rabarber/input/types/
|
15
|
-
require_relative "rabarber/input/types/
|
16
|
-
require_relative "rabarber/input/types/
|
15
|
+
require_relative "rabarber/input/types/boolean"
|
16
|
+
require_relative "rabarber/input/types/proc"
|
17
|
+
require_relative "rabarber/input/types/symbol"
|
17
18
|
|
18
19
|
require_relative "rabarber/missing/base"
|
19
20
|
require_relative "rabarber/missing/actions"
|
@@ -25,7 +26,8 @@ require_relative "rabarber/controllers/concerns/authorization"
|
|
25
26
|
require_relative "rabarber/helpers/helpers"
|
26
27
|
require_relative "rabarber/models/concerns/has_roles"
|
27
28
|
require_relative "rabarber/models/role"
|
28
|
-
|
29
|
+
|
30
|
+
require_relative "rabarber/core/permissions"
|
29
31
|
|
30
32
|
require_relative "rabarber/railtie"
|
31
33
|
|
data/rabarber.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["enjaku4", "trafium"]
|
9
9
|
spec.email = ["rabarber_gem@icloud.com"]
|
10
10
|
|
11
|
-
spec.summary = "Simple authorization library for Ruby on Rails."
|
11
|
+
spec.summary = "Simple role-based authorization library for Ruby on Rails."
|
12
12
|
spec.homepage = "https://github.com/enjaku4/rabarber"
|
13
13
|
spec.license = "MIT"
|
14
14
|
spec.required_ruby_version = ">= 3.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabarber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- enjaku4
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-03-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -38,27 +38,28 @@ files:
|
|
38
38
|
- lib/generators/rabarber/roles_generator.rb
|
39
39
|
- lib/generators/rabarber/templates/create_rabarber_roles.rb.erb
|
40
40
|
- lib/rabarber.rb
|
41
|
-
- lib/rabarber/access.rb
|
42
41
|
- lib/rabarber/cache.rb
|
43
42
|
- lib/rabarber/configuration.rb
|
44
43
|
- lib/rabarber/controllers/concerns/authorization.rb
|
44
|
+
- lib/rabarber/core/access.rb
|
45
|
+
- lib/rabarber/core/permissions.rb
|
46
|
+
- lib/rabarber/core/rule.rb
|
45
47
|
- lib/rabarber/helpers/helpers.rb
|
46
|
-
- lib/rabarber/input/
|
48
|
+
- lib/rabarber/input/action.rb
|
47
49
|
- lib/rabarber/input/base.rb
|
48
|
-
- lib/rabarber/input/
|
50
|
+
- lib/rabarber/input/dynamic_rule.rb
|
51
|
+
- lib/rabarber/input/role.rb
|
49
52
|
- lib/rabarber/input/roles.rb
|
50
|
-
- lib/rabarber/input/types/
|
51
|
-
- lib/rabarber/input/types/
|
52
|
-
- lib/rabarber/input/types/
|
53
|
+
- lib/rabarber/input/types/boolean.rb
|
54
|
+
- lib/rabarber/input/types/proc.rb
|
55
|
+
- lib/rabarber/input/types/symbol.rb
|
53
56
|
- lib/rabarber/logger.rb
|
54
57
|
- lib/rabarber/missing/actions.rb
|
55
58
|
- lib/rabarber/missing/base.rb
|
56
59
|
- lib/rabarber/missing/roles.rb
|
57
60
|
- lib/rabarber/models/concerns/has_roles.rb
|
58
61
|
- lib/rabarber/models/role.rb
|
59
|
-
- lib/rabarber/permissions.rb
|
60
62
|
- lib/rabarber/railtie.rb
|
61
|
-
- lib/rabarber/rule.rb
|
62
63
|
- lib/rabarber/version.rb
|
63
64
|
- rabarber.gemspec
|
64
65
|
homepage: https://github.com/enjaku4/rabarber
|
@@ -80,8 +81,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
81
|
- !ruby/object:Gem::Version
|
81
82
|
version: '0'
|
82
83
|
requirements: []
|
83
|
-
rubygems_version: 3.
|
84
|
+
rubygems_version: 3.3.26
|
84
85
|
signing_key:
|
85
86
|
specification_version: 4
|
86
|
-
summary: Simple authorization library for Ruby on Rails.
|
87
|
+
summary: Simple role-based authorization library for Ruby on Rails.
|
87
88
|
test_files: []
|
data/lib/rabarber/access.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Access
|
5
|
-
def access_granted?(roles, controller, action, dynamic_rule_receiver)
|
6
|
-
controller_accessible?(roles, controller, dynamic_rule_receiver) ||
|
7
|
-
action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
8
|
-
end
|
9
|
-
|
10
|
-
def controller_accessible?(roles, controller, dynamic_rule_receiver)
|
11
|
-
accessible_controllers(roles, dynamic_rule_receiver).any? do |accessible_controller|
|
12
|
-
controller <= accessible_controller
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
17
|
-
action_rules[controller].any? { |rule| rule.verify_access(roles, dynamic_rule_receiver, action) }
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def accessible_controllers(roles, dynamic_rule_receiver)
|
23
|
-
controller_rules.select { |_, rule| rule.verify_access(roles, dynamic_rule_receiver) }.keys
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class Actions < Rabarber::Input::Base
|
6
|
-
def initialize(
|
7
|
-
value,
|
8
|
-
error_type = Rabarber::InvalidArgumentError,
|
9
|
-
error_message = "Action name must be a Symbol or a String"
|
10
|
-
)
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def valid?
|
17
|
-
(value.is_a?(String) || value.is_a?(Symbol)) && value.present? || value.nil?
|
18
|
-
end
|
19
|
-
|
20
|
-
def processed_value
|
21
|
-
case value
|
22
|
-
when String, Symbol
|
23
|
-
value.to_sym
|
24
|
-
when nil
|
25
|
-
value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class DynamicRules < Rabarber::Input::Base
|
6
|
-
def initialize(
|
7
|
-
value,
|
8
|
-
error_type = Rabarber::InvalidArgumentError,
|
9
|
-
error_message = "Dynamic rule must be a Symbol, a String, or a Proc"
|
10
|
-
)
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def valid?
|
17
|
-
(value.is_a?(String) || value.is_a?(Symbol)) && value.present? || value.nil? || value.is_a?(Proc)
|
18
|
-
end
|
19
|
-
|
20
|
-
def processed_value
|
21
|
-
case value
|
22
|
-
when String, Symbol
|
23
|
-
value.to_sym
|
24
|
-
when Proc, nil
|
25
|
-
value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/rabarber/permissions.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "access"
|
4
|
-
require_relative "rule"
|
5
|
-
|
6
|
-
module Rabarber
|
7
|
-
class Permissions
|
8
|
-
include Singleton
|
9
|
-
|
10
|
-
extend Access
|
11
|
-
|
12
|
-
attr_reader :storage
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
|
19
|
-
rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
20
|
-
|
21
|
-
if action
|
22
|
-
instance.storage[:action_rules][controller] += [rule]
|
23
|
-
else
|
24
|
-
instance.storage[:controller_rules][controller] = rule
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.controller_rules
|
29
|
-
instance.storage[:controller_rules]
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.action_rules
|
33
|
-
instance.storage[:action_rules]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
data/lib/rabarber/rule.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
class Rule
|
5
|
-
attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
|
6
|
-
|
7
|
-
def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
|
8
|
-
@action = action
|
9
|
-
@roles = Array(roles)
|
10
|
-
@dynamic_rule = dynamic_rule
|
11
|
-
@negated_dynamic_rule = negated_dynamic_rule
|
12
|
-
end
|
13
|
-
|
14
|
-
def verify_access(user_roles, dynamic_rule_receiver, action_name = nil)
|
15
|
-
action_accessible?(action_name) && roles_permitted?(user_roles) && dynamic_rule_followed?(dynamic_rule_receiver)
|
16
|
-
end
|
17
|
-
|
18
|
-
def action_accessible?(action_name)
|
19
|
-
action_name.nil? || action_name == action
|
20
|
-
end
|
21
|
-
|
22
|
-
def roles_permitted?(user_roles)
|
23
|
-
return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
|
24
|
-
|
25
|
-
roles.empty? || (roles & user_roles).any?
|
26
|
-
end
|
27
|
-
|
28
|
-
def dynamic_rule_followed?(dynamic_rule_receiver)
|
29
|
-
!!(execute_dynamic_rule(dynamic_rule_receiver, false) && execute_dynamic_rule(dynamic_rule_receiver, true))
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def execute_dynamic_rule(dynamic_rule_receiver, is_negated)
|
35
|
-
rule = is_negated ? negated_dynamic_rule : dynamic_rule
|
36
|
-
|
37
|
-
return true if rule.nil?
|
38
|
-
|
39
|
-
result = if rule.is_a?(Proc)
|
40
|
-
dynamic_rule_receiver.instance_exec(&rule)
|
41
|
-
else
|
42
|
-
dynamic_rule_receiver.send(rule)
|
43
|
-
end
|
44
|
-
|
45
|
-
is_negated ? !result : result
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|