rabarber 1.3.0 → 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 +10 -3
- data/README.md +31 -23
- 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 +5 -0
- data/lib/rabarber/models/role.rb +8 -2
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +8 -6
- data/rabarber.gemspec +1 -1
- metadata +12 -11
- 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 -38
- 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,7 +1,13 @@
|
|
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
|
+
|
1
7
|
## 1.3.0
|
2
8
|
|
3
9
|
- Add methods to directly add, rename, and remove roles
|
4
|
-
- `HasRoles#assign_roles` and `HasRoles#revoke_roles` methods
|
10
|
+
- Modify `Rabarber::HasRoles#assign_roles` and `Rabarber::HasRoles#revoke_roles` methods to return the list of roles assigned to the user
|
5
11
|
- Minor performance improvements
|
6
12
|
|
7
13
|
## 1.2.2
|
@@ -14,6 +20,7 @@
|
|
14
20
|
- Cache roles to avoid unnecessary database queries
|
15
21
|
- Introduce `cache_enabled` configuration option allowing to enable or disable role caching
|
16
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
|
17
24
|
- Various minor improvements
|
18
25
|
|
19
26
|
## 1.2.0
|
@@ -42,7 +49,7 @@
|
|
42
49
|
## 1.0.2
|
43
50
|
|
44
51
|
- Various enhancements for gem development and release
|
45
|
-
- 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
|
46
53
|
|
47
54
|
## 1.0.1
|
48
55
|
|
@@ -61,7 +68,7 @@
|
|
61
68
|
|
62
69
|
## 0.1.4
|
63
70
|
|
64
|
-
- Remove `HasRoles#role?` method as unnecessary
|
71
|
+
- Remove `Rabarber::HasRoles#role?` method as unnecessary
|
65
72
|
|
66
73
|
## 0.1.3
|
67
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,7 +59,7 @@ 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|
|
@@ -76,17 +72,17 @@ Rabarber.configure do |config|
|
|
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)
|
@@ -166,7 +162,7 @@ Rabarber::Role.rename(:admin, :administrator)
|
|
166
162
|
```
|
167
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`.
|
168
164
|
|
169
|
-
The method won't rename the role if it is assigned to any user. To force the rename, use the method with `force: true` argument:
|
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:
|
170
166
|
```rb
|
171
167
|
Rabarber::Role.rename(:admin, :administrator, force: true)
|
172
168
|
```
|
@@ -181,7 +177,7 @@ Rabarber::Role.remove(:admin)
|
|
181
177
|
|
182
178
|
This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
|
183
179
|
|
184
|
-
The method won't remove the role if it is assigned to any user. To force the removal, use the method with `force: true` argument:
|
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:
|
185
181
|
```rb
|
186
182
|
Rabarber::Role.remove(:admin, force: true)
|
187
183
|
```
|
@@ -194,9 +190,17 @@ If you need to list all the role names available in your application, use:
|
|
194
190
|
Rabarber::Role.names
|
195
191
|
```
|
196
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
|
+
|
197
201
|
## Authorization Rules
|
198
202
|
|
199
|
-
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.
|
200
204
|
|
201
205
|
```rb
|
202
206
|
class ApplicationController < ActionController::Base
|
@@ -264,9 +268,9 @@ class InvoicesController < ApplicationController
|
|
264
268
|
end
|
265
269
|
```
|
266
270
|
|
267
|
-
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`.
|
268
272
|
|
269
|
-
|
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._
|
270
274
|
|
271
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.
|
272
276
|
|
@@ -301,7 +305,7 @@ class InvoicesController < ApplicationController
|
|
301
305
|
end
|
302
306
|
end
|
303
307
|
```
|
304
|
-
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.
|
305
309
|
|
306
310
|
Rules defined in child classes don't override parent rules but rather add to them:
|
307
311
|
```rb
|
@@ -319,7 +323,7 @@ This means that `Crm::InvoicesController` is still accessible to `admin` but is
|
|
319
323
|
|
320
324
|
## View Helpers
|
321
325
|
|
322
|
-
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.
|
323
327
|
|
324
328
|
```rb
|
325
329
|
module ApplicationHelper
|
@@ -344,14 +348,18 @@ The usage is straightforward:
|
|
344
348
|
|
345
349
|
## Problems?
|
346
350
|
|
347
|
-
|
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?
|
348
356
|
|
349
|
-
- **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.
|
350
358
|
- **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
|
351
359
|
|
352
360
|
## Contributing
|
353
361
|
|
354
|
-
|
362
|
+
Before opening an issue or creating a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
|
355
363
|
|
356
364
|
## License
|
357
365
|
|
@@ -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
|
data/lib/rabarber/models/role.rb
CHANGED
@@ -4,7 +4,7 @@ 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
|
|
@@ -46,6 +46,12 @@ module Rabarber
|
|
46
46
|
!!role.destroy!
|
47
47
|
end
|
48
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
|
+
|
49
55
|
private
|
50
56
|
|
51
57
|
def delete_roles_cache
|
@@ -64,7 +70,7 @@ module Rabarber
|
|
64
70
|
end
|
65
71
|
|
66
72
|
def process_role_name(name)
|
67
|
-
Rabarber::Input::
|
73
|
+
Rabarber::Input::Role.new(name).process
|
68
74
|
end
|
69
75
|
end
|
70
76
|
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.3.
|
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
|
@@ -83,5 +84,5 @@ requirements: []
|
|
83
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,38 +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
|
-
class << self
|
19
|
-
def add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
|
20
|
-
rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
21
|
-
|
22
|
-
if action
|
23
|
-
instance.storage[:action_rules][controller] += [rule]
|
24
|
-
else
|
25
|
-
instance.storage[:controller_rules][controller] = rule
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def controller_rules
|
30
|
-
instance.storage[:controller_rules]
|
31
|
-
end
|
32
|
-
|
33
|
-
def action_rules
|
34
|
-
instance.storage[:action_rules]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
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
|