rabarber 5.2.5 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +58 -41
- data/lib/rabarber/configuration.rb +50 -36
- data/lib/rabarber/controllers/concerns/authorization.rb +1 -1
- data/lib/rabarber/core/cache.rb +25 -12
- data/lib/rabarber/inputs/base.rb +4 -9
- data/lib/rabarber/inputs/boolean.rb +1 -1
- data/lib/rabarber/inputs/context.rb +7 -8
- data/lib/rabarber/inputs/contexts/authorizational.rb +13 -1
- data/lib/rabarber/inputs/dynamic_rule.rb +5 -1
- data/lib/rabarber/inputs/model.rb +6 -1
- data/lib/rabarber/inputs/non_empty_string.rb +3 -1
- data/lib/rabarber/inputs/non_empty_symbol.rb +15 -0
- data/lib/rabarber/inputs/role.rb +5 -1
- data/lib/rabarber/inputs/roles.rb +4 -2
- data/lib/rabarber/models/concerns/roleable.rb +4 -4
- data/lib/rabarber/models/role.rb +5 -29
- data/lib/rabarber/models/role_management.rb +12 -0
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +4 -10
- data/rabarber.gemspec +2 -4
- metadata +8 -35
- data/lib/rabarber/inputs/symbol.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b63470cbdf375117e5c901ae46992d0fb85868acedacde3ab17472437d8f4a10
|
|
4
|
+
data.tar.gz: bba19d48ebad8a6440c70e7555d5befcc9c64f67c0a43f0408cd966f9d24aa15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b43430eb2fc105add42baa08aa12349137f1aab6021d371400cc48c48c134db4c20bd81e3cbff043e63bf6f22161d6645ab57619090bf1c08b905f6be60fbe1
|
|
7
|
+
data.tar.gz: cb648ea4388e067d1f06d57f2ba9b45fb7139df3a5a1cbb102ae42441730a2a83749b116d468fb0c6d3b9b0be419c010a62c73c3c0be2d90752086aa5f4e8837
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## v6.0.0
|
|
2
|
+
|
|
3
|
+
### Breaking:
|
|
4
|
+
|
|
5
|
+
- Removed methods deprecated in v5.2.0 from the public API
|
|
6
|
+
- Dropped support for Ruby 3.2
|
|
7
|
+
- Dropped support for Rails 7.1
|
|
8
|
+
|
|
9
|
+
### Bugs:
|
|
10
|
+
|
|
11
|
+
- Fixed cache clearing not working with Memcached
|
|
12
|
+
|
|
13
|
+
### Misc:
|
|
14
|
+
|
|
15
|
+
- Optimized cache key generation
|
|
16
|
+
- Dropped some unnecessary dependencies
|
|
17
|
+
|
|
18
|
+
To upgrade to v6.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/101)
|
|
19
|
+
|
|
1
20
|
## v5.2.5
|
|
2
21
|
|
|
3
22
|
### Misc:
|
data/README.md
CHANGED
|
@@ -9,29 +9,23 @@ Rabarber is a role-based authorization library for Ruby on Rails. It provides a
|
|
|
9
9
|
|
|
10
10
|
**Example of Usage:**
|
|
11
11
|
|
|
12
|
-
Consider a CRM system where users with different roles have distinct access levels. For instance, the role `accountant` can access invoice data
|
|
13
|
-
|
|
14
|
-
___
|
|
15
|
-
|
|
16
|
-
And this is how your controller might look with Rabarber:
|
|
12
|
+
Consider a CRM system where users with different roles have distinct access levels. For instance, the role `accountant` can access and manage invoice data, while the role `analyst` can only view it. Here's how you'd define that with Rabarber:
|
|
17
13
|
|
|
18
14
|
```rb
|
|
19
|
-
class
|
|
20
|
-
grant_access roles: :
|
|
15
|
+
class InvoicesController < ApplicationController
|
|
16
|
+
grant_access roles: :accountant
|
|
21
17
|
|
|
22
|
-
grant_access action: :index, roles: :
|
|
18
|
+
grant_access action: :index, roles: :analyst
|
|
23
19
|
def index
|
|
24
|
-
#
|
|
20
|
+
# Accessible to accountant and analyst
|
|
25
21
|
end
|
|
26
22
|
|
|
27
|
-
def
|
|
28
|
-
#
|
|
23
|
+
def update
|
|
24
|
+
# Accessible to accountant only
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
```
|
|
32
28
|
|
|
33
|
-
This means that `admin` users can access everything in `TicketsController`, while the `manager` role can access only the `index` action.
|
|
34
|
-
|
|
35
29
|
## Table of Contents
|
|
36
30
|
|
|
37
31
|
**Gem Usage:**
|
|
@@ -87,9 +81,12 @@ Create an initializer to customize Rabarber's behavior (optional):
|
|
|
87
81
|
|
|
88
82
|
```rb
|
|
89
83
|
Rabarber.configure do |config|
|
|
90
|
-
|
|
91
|
-
config.
|
|
92
|
-
|
|
84
|
+
# Enable/disable role caching (default: true)
|
|
85
|
+
config.cache_enabled = true
|
|
86
|
+
# Method to access current user (default: :current_user)
|
|
87
|
+
config.current_user_method = :current_user
|
|
88
|
+
# User model name (default: "User")
|
|
89
|
+
config.user_model_name = "User"
|
|
93
90
|
end
|
|
94
91
|
```
|
|
95
92
|
|
|
@@ -119,7 +116,7 @@ user.revoke_roles(:admin, :manager)
|
|
|
119
116
|
user.revoke_all_roles
|
|
120
117
|
```
|
|
121
118
|
|
|
122
|
-
All role assignment methods return the list of roles currently assigned to the user.
|
|
119
|
+
All role assignment and revocation methods return the list of roles currently assigned to the user.
|
|
123
120
|
|
|
124
121
|
### Role Queries
|
|
125
122
|
|
|
@@ -139,19 +136,26 @@ User.with_role(:admin, :manager)
|
|
|
139
136
|
|
|
140
137
|
## Direct Role Management
|
|
141
138
|
|
|
142
|
-
You can also manage roles
|
|
139
|
+
You can also directly manage roles available in the application:
|
|
143
140
|
|
|
144
141
|
```rb
|
|
145
142
|
# Create a new role
|
|
146
|
-
Rabarber.create_role(:admin)
|
|
143
|
+
Rabarber.create_role(:admin)
|
|
144
|
+
# => true if created, false if already exists
|
|
147
145
|
|
|
148
146
|
# Rename a role
|
|
149
|
-
Rabarber.rename_role(:admin, :administrator)
|
|
150
|
-
|
|
147
|
+
Rabarber.rename_role(:admin, :administrator)
|
|
148
|
+
# => true if renamed, false if new name exists or role is assigned
|
|
149
|
+
|
|
150
|
+
# Force rename even if role is assigned
|
|
151
|
+
Rabarber.rename_role(:admin, :administrator, force: true)
|
|
151
152
|
|
|
152
153
|
# Remove a role
|
|
153
|
-
Rabarber.delete_role(:admin)
|
|
154
|
-
|
|
154
|
+
Rabarber.delete_role(:admin)
|
|
155
|
+
# => true if deleted, false if role is assigned
|
|
156
|
+
|
|
157
|
+
# Force deletion even if role is assigned
|
|
158
|
+
Rabarber.delete_role(:admin, force: true)
|
|
155
159
|
|
|
156
160
|
# List available roles in the global context
|
|
157
161
|
Rabarber.roles
|
|
@@ -160,19 +164,18 @@ Rabarber.roles
|
|
|
160
164
|
Rabarber.all_roles
|
|
161
165
|
```
|
|
162
166
|
|
|
163
|
-
> **Note:** Some methods have been deprecated in favor of the new API shown above. Deprecated methods still work but will be removed in a future major version. See the [changelog](https://github.com/enjaku4/rabarber/blob/main/CHANGELOG.md#v520) for the complete list of deprecated methods and their replacements.
|
|
164
|
-
|
|
165
167
|
## Authorization
|
|
166
168
|
|
|
167
169
|
### Setup
|
|
168
170
|
|
|
169
|
-
Include `Rabarber::Authorization` module in your controllers and configure
|
|
171
|
+
Include `Rabarber::Authorization` module in your controllers and configure authorization:
|
|
170
172
|
|
|
171
173
|
```rb
|
|
172
174
|
class ApplicationController < ActionController::Base
|
|
173
175
|
include Rabarber::Authorization
|
|
174
176
|
|
|
175
|
-
|
|
177
|
+
# Enable authorization check for all actions in all controllers by default
|
|
178
|
+
with_authorization
|
|
176
179
|
end
|
|
177
180
|
```
|
|
178
181
|
|
|
@@ -180,11 +183,13 @@ You can also enable authorization checks selectively. Both `with_authorization`
|
|
|
180
183
|
|
|
181
184
|
```rb
|
|
182
185
|
class TicketsController < ApplicationController
|
|
183
|
-
|
|
186
|
+
# Skip authorization for specific actions
|
|
187
|
+
skip_authorization only: [:index, :show]
|
|
184
188
|
end
|
|
185
189
|
|
|
186
190
|
class InvoicesController < ApplicationController
|
|
187
|
-
|
|
191
|
+
# Enable authorization for all actions except index
|
|
192
|
+
with_authorization except: [:index]
|
|
188
193
|
end
|
|
189
194
|
```
|
|
190
195
|
|
|
@@ -192,6 +197,8 @@ Authorization requires an authenticated user. Rabarber will raise an error if no
|
|
|
192
197
|
|
|
193
198
|
### Authorization Rules
|
|
194
199
|
|
|
200
|
+
Rabarber follows a deny-by-default principle: if no `grant_access` rule is defined for an action or controller, access is denied to everyone.
|
|
201
|
+
|
|
195
202
|
Define authorization rules using `grant_access`:
|
|
196
203
|
|
|
197
204
|
```rb
|
|
@@ -211,15 +218,17 @@ class TicketsController < ApplicationController
|
|
|
211
218
|
end
|
|
212
219
|
```
|
|
213
220
|
|
|
214
|
-
Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action:
|
|
221
|
+
Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action or controller:
|
|
215
222
|
|
|
216
223
|
```rb
|
|
217
224
|
class BaseController < ApplicationController
|
|
218
|
-
|
|
225
|
+
# Admin can access everything
|
|
226
|
+
grant_access roles: :admin
|
|
219
227
|
end
|
|
220
228
|
|
|
221
229
|
class InvoicesController < BaseController
|
|
222
|
-
|
|
230
|
+
# Accountant can also access InvoicesController (along with admin)
|
|
231
|
+
grant_access roles: :accountant
|
|
223
232
|
|
|
224
233
|
grant_access action: :index, roles: :manager
|
|
225
234
|
grant_access action: :index, roles: :supervisor
|
|
@@ -233,16 +242,19 @@ It's possible to omit roles to allow unrestricted access:
|
|
|
233
242
|
|
|
234
243
|
```rb
|
|
235
244
|
class UnrestrictedController < ApplicationController
|
|
236
|
-
|
|
245
|
+
# Allow all users to access all actions
|
|
246
|
+
grant_access
|
|
237
247
|
end
|
|
238
248
|
|
|
239
249
|
class MixedController < ApplicationController
|
|
240
|
-
|
|
250
|
+
# Unrestricted index action
|
|
251
|
+
grant_access action: :index
|
|
241
252
|
def index
|
|
242
253
|
# Accessible to all users
|
|
243
254
|
end
|
|
244
255
|
|
|
245
|
-
|
|
256
|
+
# Restricted show action
|
|
257
|
+
grant_access action: :show, roles: :member
|
|
246
258
|
def show
|
|
247
259
|
# Accessible to members only
|
|
248
260
|
end
|
|
@@ -276,7 +288,7 @@ Dynamic rules can also be used without roles at all, allowing you to define cust
|
|
|
276
288
|
|
|
277
289
|
```rb
|
|
278
290
|
class InvoicesController < ApplicationController
|
|
279
|
-
grant_access action: :update, unless: -> {
|
|
291
|
+
grant_access action: :update, unless: -> { invoice.period_closed? }
|
|
280
292
|
def update
|
|
281
293
|
# ...
|
|
282
294
|
end
|
|
@@ -289,7 +301,11 @@ class InvoicesController < ApplicationController
|
|
|
289
301
|
private
|
|
290
302
|
|
|
291
303
|
def destroy_allowed?
|
|
292
|
-
InvoicePolicy.new(current_user).destroy?(
|
|
304
|
+
InvoicePolicy.new(current_user).destroy?(invoice)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def invoice
|
|
308
|
+
@invoice ||= Invoice.find(params[:id])
|
|
293
309
|
end
|
|
294
310
|
end
|
|
295
311
|
```
|
|
@@ -307,7 +323,8 @@ class ApplicationController < ActionController::Base
|
|
|
307
323
|
private
|
|
308
324
|
|
|
309
325
|
def when_unauthorized
|
|
310
|
-
|
|
326
|
+
# Custom behavior to hide existence of protected resources
|
|
327
|
+
head :not_found
|
|
311
328
|
end
|
|
312
329
|
end
|
|
313
330
|
```
|
|
@@ -445,7 +462,7 @@ Use conditional rendering based on roles:
|
|
|
445
462
|
## Getting Help and Contributing
|
|
446
463
|
|
|
447
464
|
### Getting Help
|
|
448
|
-
Have a question or need assistance? Open a discussion in
|
|
465
|
+
Have a question or need assistance? Open a discussion in the [discussions section](https://github.com/enjaku4/rabarber/discussions) for:
|
|
449
466
|
- Usage questions
|
|
450
467
|
- Implementation guidance
|
|
451
468
|
- Feature suggestions
|
|
@@ -460,7 +477,7 @@ Found a bug? Please [create an issue](https://github.com/enjaku4/rabarber/issues
|
|
|
460
477
|
Ready to contribute? You can:
|
|
461
478
|
- Fix bugs by submitting pull requests
|
|
462
479
|
- Improve documentation
|
|
463
|
-
- Add new features (please discuss first in
|
|
480
|
+
- Add new features (please discuss first in the [discussions section](https://github.com/enjaku4/rabarber/discussions))
|
|
464
481
|
|
|
465
482
|
Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md)
|
|
466
483
|
|
|
@@ -476,5 +493,5 @@ Everyone interacting in the Rabarber project is expected to follow the [code of
|
|
|
476
493
|
|
|
477
494
|
Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
|
|
478
495
|
|
|
479
|
-
[v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
|
|
496
|
+
[v5.x.x](https://github.com/enjaku4/rabarber/blob/12a23858e974f5cc0a4ebddfc18bdf84171dd554/README.md) | [v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
|
|
480
497
|
| [v2.x.x](https://github.com/enjaku4/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/enjaku4/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
|
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "singleton"
|
|
4
4
|
|
|
5
5
|
module Rabarber
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
6
|
+
class Configuration
|
|
7
|
+
include Singleton
|
|
8
|
+
|
|
9
|
+
attr_reader :cache_enabled, :current_user_method, :user_model_name
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
reset_to_defaults!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cache_enabled=(value)
|
|
16
|
+
@cache_enabled = Rabarber::Inputs::Boolean.new(
|
|
17
|
+
value,
|
|
18
|
+
error: Rabarber::ConfigurationError,
|
|
19
|
+
message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
|
|
20
|
+
).process
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def current_user_method=(value)
|
|
24
|
+
@current_user_method = Rabarber::Inputs::NonEmptySymbol.new(
|
|
25
|
+
value,
|
|
26
|
+
error: Rabarber::ConfigurationError,
|
|
27
|
+
message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
|
|
28
|
+
).process
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def user_model_name=(value)
|
|
32
|
+
@user_model_name = Rabarber::Inputs::NonEmptyString.new(
|
|
33
|
+
value,
|
|
34
|
+
error: Rabarber::ConfigurationError,
|
|
35
|
+
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
|
|
36
|
+
).process
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_to_defaults!
|
|
40
|
+
self.cache_enabled = true
|
|
41
|
+
self.current_user_method = :current_user
|
|
42
|
+
self.user_model_name = "User"
|
|
43
|
+
end
|
|
41
44
|
|
|
42
45
|
def user_model
|
|
43
46
|
Rabarber::Inputs::Model.new(
|
|
@@ -46,5 +49,16 @@ module Rabarber
|
|
|
46
49
|
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{user_model_name.inspect}"
|
|
47
50
|
).process
|
|
48
51
|
end
|
|
52
|
+
|
|
53
|
+
def configure
|
|
54
|
+
yield self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
delegate :cache_enabled, :current_user_method, :user_model_name,
|
|
59
|
+
:cache_enabled=, :current_user_method=, :user_model_name=,
|
|
60
|
+
:user_model, :reset_to_defaults!, :configure,
|
|
61
|
+
to: :instance
|
|
62
|
+
end
|
|
49
63
|
end
|
|
50
64
|
end
|
data/lib/rabarber/core/cache.rb
CHANGED
|
@@ -1,38 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "digest/
|
|
3
|
+
require "digest/md5"
|
|
4
|
+
require "securerandom"
|
|
4
5
|
|
|
5
6
|
module Rabarber
|
|
6
7
|
module Core
|
|
7
8
|
module Cache
|
|
8
|
-
|
|
9
|
+
extend self
|
|
9
10
|
|
|
10
|
-
def fetch(
|
|
11
|
+
def fetch(roleable_id, scope, &)
|
|
11
12
|
return yield unless enabled?
|
|
12
13
|
|
|
13
|
-
Rails.cache.fetch(prepare_key(
|
|
14
|
+
Rails.cache.fetch(prepare_key(roleable_id, scope), expires_in: 1.hour, race_condition_ttl: 5.seconds, &)
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
def delete(*
|
|
17
|
+
def delete(*pairs)
|
|
17
18
|
return unless enabled?
|
|
18
19
|
|
|
19
|
-
Rails.cache.delete_multi(
|
|
20
|
+
Rails.cache.delete_multi(pairs.map { |roleable_id, scope| prepare_key(roleable_id, scope) }) if pairs.any?
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
def clear
|
|
24
|
+
Rails.cache.write(VERSION_KEY, SecureRandom.alphanumeric(8))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
22
29
|
def enabled?
|
|
23
30
|
Rabarber::Configuration.cache_enabled
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
def
|
|
27
|
-
|
|
33
|
+
def prepare_key(roleable_id, scope)
|
|
34
|
+
Digest::MD5.base64digest(Marshal.dump([current_version, roleable_id, scope]))
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
def
|
|
31
|
-
|
|
37
|
+
def current_version
|
|
38
|
+
version = Rails.cache.read(VERSION_KEY).presence
|
|
39
|
+
|
|
40
|
+
return version if version
|
|
41
|
+
|
|
42
|
+
clear
|
|
43
|
+
|
|
44
|
+
Rails.cache.read(VERSION_KEY)
|
|
32
45
|
end
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
private_constant :
|
|
47
|
+
VERSION_KEY = "rabarber"
|
|
48
|
+
private_constant :VERSION_KEY
|
|
36
49
|
end
|
|
37
50
|
end
|
|
38
51
|
|
data/lib/rabarber/inputs/base.rb
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "dry-types"
|
|
4
|
-
|
|
5
3
|
module Rabarber
|
|
6
4
|
module Inputs
|
|
7
5
|
class Base
|
|
8
|
-
include Dry.Types()
|
|
9
|
-
|
|
10
6
|
def initialize(value, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
|
|
11
7
|
@value = value
|
|
12
8
|
@optional = optional
|
|
@@ -15,15 +11,14 @@ module Rabarber
|
|
|
15
11
|
end
|
|
16
12
|
|
|
17
13
|
def process
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
raise_error
|
|
14
|
+
return @value if @value.nil? && @optional
|
|
15
|
+
|
|
16
|
+
validate_and_normalize
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
private
|
|
25
20
|
|
|
26
|
-
def
|
|
21
|
+
def validate_and_normalize
|
|
27
22
|
raise NotImplementedError
|
|
28
23
|
end
|
|
29
24
|
|
|
@@ -19,14 +19,13 @@ module Rabarber
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
self.class::Nil
|
|
22
|
+
def validate_and_normalize
|
|
23
|
+
return @value if @value.nil?
|
|
24
|
+
return @value if @value.is_a?(Class)
|
|
25
|
+
return @value if @value.is_a?(ActiveRecord::Base)
|
|
26
|
+
return @value if @value in { context_type: String | nil, context_id: String | Integer | nil }
|
|
27
|
+
|
|
28
|
+
raise_error
|
|
30
29
|
end
|
|
31
30
|
end
|
|
32
31
|
end
|
|
@@ -4,9 +4,21 @@ module Rabarber
|
|
|
4
4
|
module Inputs
|
|
5
5
|
module Contexts
|
|
6
6
|
class Authorizational < Rabarber::Inputs::Context
|
|
7
|
+
def resolve
|
|
8
|
+
result = process
|
|
9
|
+
return result if result.is_a?(Symbol) || result.is_a?(Proc)
|
|
10
|
+
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
private
|
|
8
15
|
|
|
9
|
-
def
|
|
16
|
+
def validate_and_normalize
|
|
17
|
+
return @value if @value.is_a?(Proc)
|
|
18
|
+
return @value.to_sym if (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.present?
|
|
19
|
+
|
|
20
|
+
super
|
|
21
|
+
end
|
|
10
22
|
end
|
|
11
23
|
end
|
|
12
24
|
end
|
|
@@ -5,7 +5,11 @@ module Rabarber
|
|
|
5
5
|
class DynamicRule < Rabarber::Inputs::Base
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
return @value if @value.is_a?(Proc)
|
|
10
|
+
|
|
11
|
+
Rabarber::Inputs::NonEmptySymbol.new(@value, error: @error, message: @message).process
|
|
12
|
+
end
|
|
9
13
|
end
|
|
10
14
|
end
|
|
11
15
|
end
|
|
@@ -5,7 +5,12 @@ module Rabarber
|
|
|
5
5
|
class Model < Rabarber::Inputs::Base
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
model = @value.try(:safe_constantize)
|
|
10
|
+
raise_error unless model.is_a?(Class) && model < ActiveRecord::Base
|
|
11
|
+
|
|
12
|
+
model
|
|
13
|
+
end
|
|
9
14
|
end
|
|
10
15
|
end
|
|
11
16
|
end
|
|
@@ -5,7 +5,9 @@ module Rabarber
|
|
|
5
5
|
class NonEmptyString < Rabarber::Inputs::Base
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
@value.is_a?(String) && @value.present? ? @value : raise_error
|
|
10
|
+
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rabarber
|
|
4
|
+
module Inputs
|
|
5
|
+
class NonEmptySymbol < Rabarber::Inputs::Base
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
raise_error unless (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.present?
|
|
10
|
+
|
|
11
|
+
@value.to_sym
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/rabarber/inputs/role.rb
CHANGED
|
@@ -5,7 +5,11 @@ module Rabarber
|
|
|
5
5
|
class Role < Rabarber::Inputs::Base
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
raise_error unless (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.to_s.match?(/\A[a-z0-9_]+\z/)
|
|
10
|
+
|
|
11
|
+
@value.to_sym
|
|
12
|
+
end
|
|
9
13
|
end
|
|
10
14
|
end
|
|
11
15
|
end
|
|
@@ -5,8 +5,10 @@ module Rabarber
|
|
|
5
5
|
class Roles < Rabarber::Inputs::Base
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
8
|
+
def validate_and_normalize
|
|
9
|
+
Array(@value).map do |role|
|
|
10
|
+
Rabarber::Inputs::Role.new(role, error: @error, message: @message).process
|
|
11
|
+
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -40,11 +40,11 @@ module Rabarber
|
|
|
40
40
|
|
|
41
41
|
def roles(context: nil)
|
|
42
42
|
processed_context = process_context(context)
|
|
43
|
-
Rabarber::Core::Cache.fetch(
|
|
43
|
+
Rabarber::Core::Cache.fetch(roleable_id, processed_context) { rabarber_roles.list(context: processed_context) }
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def all_roles
|
|
47
|
-
Rabarber::Core::Cache.fetch(
|
|
47
|
+
Rabarber::Core::Cache.fetch(roleable_id, :all) { rabarber_roles.list_all }
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def has_role?(*role_names, context: nil)
|
|
@@ -60,7 +60,7 @@ module Rabarber
|
|
|
60
60
|
create_new_roles(processed_role_names, context: processed_context) if create_new
|
|
61
61
|
|
|
62
62
|
roles_to_assign = Rabarber::Role.where(
|
|
63
|
-
name: (processed_role_names - rabarber_roles.
|
|
63
|
+
name: (processed_role_names - rabarber_roles.list(context: processed_context)), **processed_context
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
if roles_to_assign.any?
|
|
@@ -76,7 +76,7 @@ module Rabarber
|
|
|
76
76
|
processed_context = process_context(context)
|
|
77
77
|
|
|
78
78
|
roles_to_revoke = Rabarber::Role.where(
|
|
79
|
-
name: processed_role_names.intersection(rabarber_roles.
|
|
79
|
+
name: processed_role_names.intersection(rabarber_roles.list(context: processed_context)), **processed_context
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
if roles_to_revoke.any?
|
data/lib/rabarber/models/role.rb
CHANGED
|
@@ -11,15 +11,11 @@ module Rabarber
|
|
|
11
11
|
join_table: "rabarber_roles_roleables"
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
|
-
def
|
|
15
|
-
deprecation_warning("names", "Rabarber.roles")
|
|
16
|
-
|
|
14
|
+
def list(context: nil)
|
|
17
15
|
where(process_context(context)).pluck(:name).map(&:to_sym)
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
def
|
|
21
|
-
deprecation_warning("all_names", "Rabarber.all_roles")
|
|
22
|
-
|
|
18
|
+
def list_all
|
|
23
19
|
includes(:context).each_with_object({}) do |role, hash|
|
|
24
20
|
(hash[role.context] ||= []) << role.name.to_sym
|
|
25
21
|
rescue ActiveRecord::RecordNotFound
|
|
@@ -29,9 +25,7 @@ module Rabarber
|
|
|
29
25
|
raise Rabarber::NotFoundError, "Context not found: class #{e.name} may have been renamed or deleted"
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
def
|
|
33
|
-
deprecation_warning("add", "Rabarber.create_role")
|
|
34
|
-
|
|
28
|
+
def register(name, context: nil)
|
|
35
29
|
name = process_role_name(name)
|
|
36
30
|
processed_context = process_context(context)
|
|
37
31
|
|
|
@@ -40,9 +34,7 @@ module Rabarber
|
|
|
40
34
|
!!create!(name:, **processed_context)
|
|
41
35
|
end
|
|
42
36
|
|
|
43
|
-
def
|
|
44
|
-
deprecation_warning("rename", "Rabarber.rename_role")
|
|
45
|
-
|
|
37
|
+
def amend(old_name, new_name, context: nil, force: false)
|
|
46
38
|
processed_context = process_context(context)
|
|
47
39
|
role = find_by(name: process_role_name(old_name), **processed_context)
|
|
48
40
|
|
|
@@ -57,9 +49,7 @@ module Rabarber
|
|
|
57
49
|
role.update!(name:)
|
|
58
50
|
end
|
|
59
51
|
|
|
60
|
-
def
|
|
61
|
-
deprecation_warning("remove", "Rabarber.delete_role")
|
|
62
|
-
|
|
52
|
+
def drop(name, context: nil, force: false)
|
|
63
53
|
processed_context = process_context(context)
|
|
64
54
|
role = find_by(name: process_role_name(name), **processed_context)
|
|
65
55
|
|
|
@@ -72,12 +62,6 @@ module Rabarber
|
|
|
72
62
|
!!role.destroy!
|
|
73
63
|
end
|
|
74
64
|
|
|
75
|
-
def assignees(name, context: nil)
|
|
76
|
-
deprecation_warning("assignees", "#{Rabarber::Configuration.user_model_name}.with_role")
|
|
77
|
-
|
|
78
|
-
find_by(name: process_role_name(name), **process_context(context))&.roleables || Rabarber::Configuration.user_model.none
|
|
79
|
-
end
|
|
80
|
-
|
|
81
65
|
def prune
|
|
82
66
|
orphaned_roles = where.not(context_id: nil).includes(:context).filter_map do |role|
|
|
83
67
|
!role.context
|
|
@@ -99,14 +83,6 @@ module Rabarber
|
|
|
99
83
|
|
|
100
84
|
private
|
|
101
85
|
|
|
102
|
-
def deprecation_warning(method, alternative)
|
|
103
|
-
callers = caller_locations.map(&:label)
|
|
104
|
-
|
|
105
|
-
return if callers.include?(alternative) || callers.include?("ActiveRecord::Relation#scoping")
|
|
106
|
-
|
|
107
|
-
ActiveSupport::Deprecation.new("6.0.0", "rabarber").warn("`Rabarber::Role.#{method}` method is deprecated and will be removed in the next major version, use `#{alternative}` instead.")
|
|
108
|
-
end
|
|
109
|
-
|
|
110
86
|
def delete_roleables_cache(role, context:)
|
|
111
87
|
Rabarber::Core::Cache.delete(*role.roleables.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
|
|
112
88
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rabarber
|
|
4
|
+
module RoleManagement
|
|
5
|
+
def roles(context: nil) = Rabarber::Role.list(context:)
|
|
6
|
+
def all_roles = Rabarber::Role.list_all
|
|
7
|
+
def create_role(name, context: nil) = Rabarber::Role.register(name, context:)
|
|
8
|
+
def rename_role(old_name, new_name, context: nil, force: false) = Rabarber::Role.amend(old_name, new_name, context:, force:)
|
|
9
|
+
def delete_role(name, context: nil, force: false) = Rabarber::Role.drop(name, context:, force:)
|
|
10
|
+
def prune = Rabarber::Role.prune # rubocop:disable Rails/Delegate
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/rabarber/version.rb
CHANGED
data/lib/rabarber.rb
CHANGED
|
@@ -14,7 +14,7 @@ require_relative "rabarber/inputs/model"
|
|
|
14
14
|
require_relative "rabarber/inputs/non_empty_string"
|
|
15
15
|
require_relative "rabarber/inputs/role"
|
|
16
16
|
require_relative "rabarber/inputs/roles"
|
|
17
|
-
require_relative "rabarber/inputs/
|
|
17
|
+
require_relative "rabarber/inputs/non_empty_symbol"
|
|
18
18
|
|
|
19
19
|
require_relative "rabarber/configuration"
|
|
20
20
|
|
|
@@ -27,15 +27,6 @@ module Rabarber
|
|
|
27
27
|
|
|
28
28
|
delegate :configure, to: Rabarber::Configuration
|
|
29
29
|
module_function :configure
|
|
30
|
-
|
|
31
|
-
class << self
|
|
32
|
-
def roles(context: nil) = Rabarber::Role.names(context:)
|
|
33
|
-
def all_roles = Rabarber::Role.all_names
|
|
34
|
-
def create_role(name, context: nil) = Rabarber::Role.add(name, context:)
|
|
35
|
-
def rename_role(old_name, new_name, context: nil, force: false) = Rabarber::Role.rename(old_name, new_name, context:, force:)
|
|
36
|
-
def delete_role(name, context: nil, force: false) = Rabarber::Role.remove(name, context:, force:)
|
|
37
|
-
def prune = Rabarber::Role.prune
|
|
38
|
-
end
|
|
39
30
|
end
|
|
40
31
|
|
|
41
32
|
require_relative "rabarber/core/cache"
|
|
@@ -47,6 +38,9 @@ require_relative "rabarber/helpers/helpers"
|
|
|
47
38
|
require_relative "rabarber/helpers/migration_helpers"
|
|
48
39
|
require_relative "rabarber/models/concerns/roleable"
|
|
49
40
|
require_relative "rabarber/models/role"
|
|
41
|
+
require_relative "rabarber/models/role_management"
|
|
42
|
+
|
|
43
|
+
Rabarber.extend Rabarber::RoleManagement
|
|
50
44
|
|
|
51
45
|
require_relative "rabarber/core/permissions"
|
|
52
46
|
|
data/rabarber.gemspec
CHANGED
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.summary = "Simple role-based authorization library for Ruby on Rails"
|
|
20
20
|
spec.description = "Simple role-based authorization for Rails applications with multi-tenancy support"
|
|
21
21
|
spec.license = "MIT"
|
|
22
|
-
spec.required_ruby_version = ">= 3.
|
|
22
|
+
spec.required_ruby_version = ">= 3.3", "< 4.1"
|
|
23
23
|
|
|
24
24
|
spec.files = [
|
|
25
25
|
"rabarber.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
|
|
@@ -27,7 +27,5 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
|
|
28
28
|
spec.require_paths = ["lib"]
|
|
29
29
|
|
|
30
|
-
spec.add_dependency "
|
|
31
|
-
spec.add_dependency "dry-types", "~> 1.7"
|
|
32
|
-
spec.add_dependency "rails", ">= 7.1", "< 8.2"
|
|
30
|
+
spec.add_dependency "rails", ">= 7.2", "< 8.2"
|
|
33
31
|
end
|
metadata
CHANGED
|
@@ -1,50 +1,22 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rabarber
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- enjaku4
|
|
8
8
|
- trafium
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: dry-configurable
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.1'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.1'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: dry-types
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.7'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.7'
|
|
41
13
|
- !ruby/object:Gem::Dependency
|
|
42
14
|
name: rails
|
|
43
15
|
requirement: !ruby/object:Gem::Requirement
|
|
44
16
|
requirements:
|
|
45
17
|
- - ">="
|
|
46
18
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '7.
|
|
19
|
+
version: '7.2'
|
|
48
20
|
- - "<"
|
|
49
21
|
- !ruby/object:Gem::Version
|
|
50
22
|
version: '8.2'
|
|
@@ -54,7 +26,7 @@ dependencies:
|
|
|
54
26
|
requirements:
|
|
55
27
|
- - ">="
|
|
56
28
|
- !ruby/object:Gem::Version
|
|
57
|
-
version: '7.
|
|
29
|
+
version: '7.2'
|
|
58
30
|
- - "<"
|
|
59
31
|
- !ruby/object:Gem::Version
|
|
60
32
|
version: '8.2'
|
|
@@ -88,11 +60,12 @@ files:
|
|
|
88
60
|
- lib/rabarber/inputs/dynamic_rule.rb
|
|
89
61
|
- lib/rabarber/inputs/model.rb
|
|
90
62
|
- lib/rabarber/inputs/non_empty_string.rb
|
|
63
|
+
- lib/rabarber/inputs/non_empty_symbol.rb
|
|
91
64
|
- lib/rabarber/inputs/role.rb
|
|
92
65
|
- lib/rabarber/inputs/roles.rb
|
|
93
|
-
- lib/rabarber/inputs/symbol.rb
|
|
94
66
|
- lib/rabarber/models/concerns/roleable.rb
|
|
95
67
|
- lib/rabarber/models/role.rb
|
|
68
|
+
- lib/rabarber/models/role_management.rb
|
|
96
69
|
- lib/rabarber/railtie.rb
|
|
97
70
|
- lib/rabarber/version.rb
|
|
98
71
|
- rabarber.gemspec
|
|
@@ -115,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
115
88
|
requirements:
|
|
116
89
|
- - ">="
|
|
117
90
|
- !ruby/object:Gem::Version
|
|
118
|
-
version: '3.
|
|
91
|
+
version: '3.3'
|
|
119
92
|
- - "<"
|
|
120
93
|
- !ruby/object:Gem::Version
|
|
121
94
|
version: '4.1'
|
|
@@ -125,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
125
98
|
- !ruby/object:Gem::Version
|
|
126
99
|
version: '0'
|
|
127
100
|
requirements: []
|
|
128
|
-
rubygems_version:
|
|
101
|
+
rubygems_version: 4.0.8
|
|
129
102
|
specification_version: 4
|
|
130
103
|
summary: Simple role-based authorization library for Ruby on Rails
|
|
131
104
|
test_files: []
|