rabarber 5.1.1 → 5.2.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 +27 -3
- data/README.md +81 -48
- data/lib/rabarber/configuration.rb +8 -12
- data/lib/rabarber/controllers/concerns/authorization.rb +18 -19
- data/lib/rabarber/core/permissions.rb +1 -1
- data/lib/rabarber/core/rule.rb +13 -10
- data/lib/rabarber/inputs/base.rb +35 -0
- data/lib/rabarber/inputs/boolean.rb +11 -0
- data/lib/rabarber/inputs/context.rb +33 -0
- data/lib/rabarber/inputs/contexts/authorizational.rb +13 -0
- data/lib/rabarber/inputs/dynamic_rule.rb +11 -0
- data/lib/rabarber/inputs/model.rb +11 -0
- data/lib/rabarber/inputs/non_empty_string.rb +11 -0
- data/lib/rabarber/inputs/role.rb +11 -0
- data/lib/rabarber/inputs/roles.rb +13 -0
- data/lib/rabarber/inputs/symbol.rb +11 -0
- data/lib/rabarber/models/concerns/{has_roles.rb → roleable.rb} +29 -19
- data/lib/rabarber/models/role.rb +41 -9
- data/lib/rabarber/railtie.rb +9 -17
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +22 -4
- data/rabarber.gemspec +3 -3
- metadata +21 -13
- data/lib/rabarber/core/integrity_checker.rb +0 -44
- data/lib/rabarber/inputs.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a773c884c0e207de25a518ff864d25ff13cd1a05c712c88efd17d264782d52
|
4
|
+
data.tar.gz: 901200cff398f2355a307aff03c9e63f1bcdbecf07369bf768fe7f12ebed9151
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd83d11d1c831f3ecb5ea36fb6d650113453f5e30b5cfdfb1affb58a515a5f77da33eb067a03abde379997f47c7e8c44d5abdf05c3a1b363d778215241406ad0
|
7
|
+
data.tar.gz: 130c1c65c3d5bc2e33b788e35f4a790586784ced24a20a110b106303729a2c5a798330e5e05abfe6ebf58902a63761af99909443a92b2dcec496f6d26136926e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## v5.2.0
|
2
|
+
|
3
|
+
### Features:
|
4
|
+
|
5
|
+
- Added `Rabarber.prune` method to allow manual pruning of roles with deleted contexts
|
6
|
+
|
7
|
+
### Misc:
|
8
|
+
|
9
|
+
- `revoke_all_roles` now returns an empty array instead of `nil` for consistency with other role assignment methods
|
10
|
+
- Deprecated the following role management methods in favor of new counterparts:
|
11
|
+
- `Rabarber::Role.names` -> `Rabarber.roles`
|
12
|
+
- `Rabarber::Role.all_names` -> `Rabarber.all_roles`
|
13
|
+
- `Rabarber::Role.add` -> `Rabarber.create_role`
|
14
|
+
- `Rabarber::Role.rename` -> `Rabarber.rename_role`
|
15
|
+
- `Rabarber::Role.remove` -> `Rabarber.delete_role`
|
16
|
+
- `Rabarber::Role.assignees` -> `User.with_role`
|
17
|
+
|
18
|
+
## v5.1.2
|
19
|
+
|
20
|
+
### Misc:
|
21
|
+
|
22
|
+
- Minor performance improvements and code cleanup
|
23
|
+
- Relaxed dependency versions
|
24
|
+
|
1
25
|
## v5.1.1
|
2
26
|
|
3
27
|
### Misc:
|
@@ -28,7 +52,7 @@
|
|
28
52
|
- Added `with_authorization` method for more granular authorization control
|
29
53
|
- `Rabarber::Role.rename` and `Rabarber::Role.remove` now require the role to exist
|
30
54
|
|
31
|
-
To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/
|
55
|
+
To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/77)
|
32
56
|
|
33
57
|
### Features:
|
34
58
|
|
@@ -133,7 +157,7 @@ To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/b
|
|
133
157
|
|
134
158
|
- Changed Rabarber roles table structure
|
135
159
|
|
136
|
-
To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/
|
160
|
+
To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/58)
|
137
161
|
|
138
162
|
### Features:
|
139
163
|
|
@@ -157,7 +181,7 @@ To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/b
|
|
157
181
|
- Replaced `when_unauthorized` configuration option with an overridable controller method
|
158
182
|
- Renamed `Rabarber::Role.assignees_for` method to `Rabarber::Role.assignees`
|
159
183
|
|
160
|
-
To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/
|
184
|
+
To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/52)
|
161
185
|
|
162
186
|
### Features:
|
163
187
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Rabarber: Role-Based Authorization for Rails
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/rabarber)
|
4
|
-
[](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
|
5
5
|
|
6
6
|
Rabarber is a role-based authorization library for Ruby on Rails that focuses on controller-level access control. Rather than answering domain questions like "can this user create a post?", Rabarber answers "can this user access the create post endpoint?", providing a clean separation between authorization and business logic.
|
7
7
|
|
@@ -25,7 +25,6 @@ Rabarber is a role-based authorization library for Ruby on Rails that focuses on
|
|
25
25
|
- [Multi-tenancy / Context](#multi-tenancy--context)
|
26
26
|
- [View Helpers](#view-helpers)
|
27
27
|
|
28
|
-
|
29
28
|
**Community Resources:**
|
30
29
|
- [Getting Help and Contributing](#getting-help-and-contributing)
|
31
30
|
- [License](#license)
|
@@ -68,13 +67,13 @@ Configure Rabarber in an initializer if customization is needed:
|
|
68
67
|
|
69
68
|
```rb
|
70
69
|
Rabarber.configure do |config|
|
71
|
-
config.cache_enabled = true # Enable role caching (default: true)
|
70
|
+
config.cache_enabled = true # Enable/disable role caching (default: true)
|
72
71
|
config.current_user_method = :current_user # Method to access current user (default: :current_user)
|
73
72
|
config.user_model_name = "User" # User model name (default: "User")
|
74
73
|
end
|
75
74
|
```
|
76
75
|
|
77
|
-
To clear the role cache manually:
|
76
|
+
Roles are cached by default for performance. To clear the role cache manually:
|
78
77
|
|
79
78
|
```rb
|
80
79
|
Rabarber::Cache.clear
|
@@ -100,17 +99,22 @@ user.revoke_roles(:accountant, :manager)
|
|
100
99
|
user.revoke_all_roles
|
101
100
|
```
|
102
101
|
|
102
|
+
All role assignment methods return the list of roles currently assigned to the user.
|
103
|
+
|
103
104
|
### Role Queries
|
104
105
|
|
105
106
|
```rb
|
106
107
|
# Check if user has any of the specified roles
|
107
108
|
user.has_role?(:accountant, :manager)
|
108
109
|
|
109
|
-
# Get user's roles
|
110
|
+
# Get user's roles in the global context
|
110
111
|
user.roles
|
111
112
|
|
112
|
-
# Get all roles grouped by context
|
113
|
+
# Get all user's roles grouped by context
|
113
114
|
user.all_roles
|
115
|
+
|
116
|
+
# Get users with any of the specified roles
|
117
|
+
User.with_role(:admin, :manager)
|
114
118
|
```
|
115
119
|
|
116
120
|
## Role Management
|
@@ -119,24 +123,25 @@ user.all_roles
|
|
119
123
|
|
120
124
|
```rb
|
121
125
|
# Create a new role
|
122
|
-
Rabarber
|
126
|
+
Rabarber.create_role(:admin) # => true if created, false if already exists
|
123
127
|
|
124
128
|
# Rename a role
|
125
|
-
Rabarber
|
126
|
-
Rabarber
|
129
|
+
Rabarber.rename_role(:admin, :administrator) # => true if renamed, false if new name exists or role is assigned
|
130
|
+
Rabarber.rename_role(:admin, :administrator, force: true) # Force rename even if role is assigned
|
127
131
|
|
128
132
|
# Remove a role
|
129
|
-
Rabarber
|
130
|
-
Rabarber
|
133
|
+
Rabarber.delete_role(:admin) # => true if deleted, false if role is assigned
|
134
|
+
Rabarber.delete_role(:admin, force: true) # Force deletion even if role is assigned
|
131
135
|
|
132
|
-
# List available roles
|
133
|
-
Rabarber
|
134
|
-
Rabarber::Role.all_names # All roles grouped by context
|
136
|
+
# List available roles in the global context
|
137
|
+
Rabarber.roles
|
135
138
|
|
136
|
-
#
|
137
|
-
Rabarber
|
139
|
+
# List all available roles grouped by context
|
140
|
+
Rabarber.all_roles
|
138
141
|
```
|
139
142
|
|
143
|
+
> **Note:** Some methods have been deprecated in favor of the new API shown above. The 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 new counterparts.
|
144
|
+
|
140
145
|
## Controller Authorization
|
141
146
|
|
142
147
|
### Basic Setup
|
@@ -155,7 +160,7 @@ class InvoicesController < ApplicationController
|
|
155
160
|
end
|
156
161
|
```
|
157
162
|
|
158
|
-
Authorization requires an authenticated user.
|
163
|
+
Authorization requires an authenticated user to be present.
|
159
164
|
|
160
165
|
### Skip Authorization
|
161
166
|
|
@@ -182,9 +187,8 @@ class TicketsController < ApplicationController
|
|
182
187
|
# Accessible to admin, manager, and support roles
|
183
188
|
end
|
184
189
|
|
185
|
-
grant_access action: :destroy, roles: :owner, context: -> { Ticket.find(params[:id]) }
|
186
190
|
def destroy
|
187
|
-
# Accessible to admin
|
191
|
+
# Accessible to admin role only
|
188
192
|
end
|
189
193
|
end
|
190
194
|
```
|
@@ -215,7 +219,7 @@ Omit roles to allow unrestricted access:
|
|
215
219
|
|
216
220
|
```rb
|
217
221
|
class UnrestrictedController < ApplicationController
|
218
|
-
grant_access # Allow all users
|
222
|
+
grant_access # Allow all users to access all actions
|
219
223
|
end
|
220
224
|
|
221
225
|
class MixedController < ApplicationController
|
@@ -244,14 +248,12 @@ class ApplicationController < ActionController::Base
|
|
244
248
|
private
|
245
249
|
|
246
250
|
def when_unauthorized
|
247
|
-
#
|
248
|
-
# Custom behavior example:
|
249
|
-
head :not_found # Hide existence of protected resources
|
251
|
+
head :not_found # Custom behavior to hide existence of protected resources
|
250
252
|
end
|
251
253
|
end
|
252
254
|
```
|
253
255
|
|
254
|
-
By default, Rabarber will redirect back (HTML format) or return 403 (other formats).
|
256
|
+
By default, when unauthorized, Rabarber will redirect back (HTML format) or return 403 (other formats).
|
255
257
|
|
256
258
|
## Dynamic Rules
|
257
259
|
|
@@ -263,13 +265,13 @@ class OrdersController < ApplicationController
|
|
263
265
|
grant_access roles: :manager, if: :company_manager?, unless: :suspended?
|
264
266
|
|
265
267
|
# Proc-based conditions
|
266
|
-
grant_access action: :show, roles: :client, if: -> { current_user.
|
268
|
+
grant_access action: :show, roles: :client, if: -> { current_user.company == Order.find(params[:id]).company }
|
267
269
|
def show
|
268
270
|
# Accessible to company managers unless suspended, and to clients if the client's company matches the order's company
|
269
271
|
end
|
270
272
|
|
271
273
|
# Dynamic-only rules (no roles required, can be used with custom policies)
|
272
|
-
grant_access action: :index, if: -> { OrdersPolicy.new(current_user).
|
274
|
+
grant_access action: :index, if: -> { OrdersPolicy.new(current_user).index? }
|
273
275
|
def index
|
274
276
|
# Accessible to company managers unless suspended, and to users based on custom policy logic
|
275
277
|
end
|
@@ -288,7 +290,7 @@ end
|
|
288
290
|
|
289
291
|
## Multi-tenancy / Context
|
290
292
|
|
291
|
-
All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes
|
293
|
+
All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes. By default, context is `nil`, meaning roles are global.
|
292
294
|
|
293
295
|
### Contextual Role Assignment
|
294
296
|
|
@@ -297,32 +299,52 @@ All Rabarber methods accept a `context` parameter, allowing you to work with rol
|
|
297
299
|
user.assign_roles(:owner, context: project)
|
298
300
|
user.assign_roles(:member, context: project)
|
299
301
|
|
300
|
-
# Assign roles within a model class
|
302
|
+
# Assign roles within a model class
|
301
303
|
user.assign_roles(:admin, context: Project)
|
302
304
|
|
303
305
|
# Check contextual roles
|
304
306
|
user.has_role?(:owner, context: project)
|
305
307
|
user.has_role?(:admin, context: Project)
|
306
308
|
|
307
|
-
#
|
308
|
-
user.
|
309
|
-
|
309
|
+
# Revoke roles
|
310
|
+
user.revoke_roles(:owner, context: project)
|
311
|
+
|
312
|
+
# Get user roles
|
313
|
+
user.roles(context: Project)
|
314
|
+
|
315
|
+
# Get users with a role
|
316
|
+
User.with_role(:member, context: project)
|
317
|
+
```
|
318
|
+
|
319
|
+
### Contextual Role Management
|
320
|
+
|
321
|
+
```rb
|
322
|
+
# Create a new role within a context
|
323
|
+
Rabarber.create_role(:admin, context: Project)
|
324
|
+
|
325
|
+
# Rename a role within a context
|
326
|
+
Rabarber.rename_role(:admin, :owner, context: project)
|
327
|
+
|
328
|
+
# Remove a contextual role
|
329
|
+
Rabarber.delete_role(:admin, context: project)
|
330
|
+
|
331
|
+
# List available roles within a specific context
|
332
|
+
Rabarber.roles(context: project)
|
310
333
|
```
|
311
334
|
|
312
335
|
### Contextual Authorization
|
313
336
|
|
314
337
|
```rb
|
315
338
|
class ProjectsController < ApplicationController
|
316
|
-
# Class-based context
|
317
339
|
grant_access roles: :admin, context: Project
|
318
340
|
|
319
|
-
#
|
341
|
+
# Method-based context resolution
|
320
342
|
grant_access action: :show, roles: :member, context: :current_project
|
321
343
|
def show
|
322
344
|
# Accessible to Project admin and members of the current project
|
323
345
|
end
|
324
346
|
|
325
|
-
#
|
347
|
+
# Proc-based context resolution
|
326
348
|
grant_access action: :update, roles: :owner, context: -> { Project.find(params[:id]) }
|
327
349
|
def update
|
328
350
|
# Accessible to Project admin and owner of the current project
|
@@ -336,16 +358,26 @@ class ProjectsController < ApplicationController
|
|
336
358
|
end
|
337
359
|
```
|
338
360
|
|
361
|
+
### Orphaned Context
|
362
|
+
|
363
|
+
When a context object is deleted from your database, its associated roles become orphaned and ignored by Rabarber.
|
364
|
+
|
365
|
+
To clean up orphaned context roles, use:
|
366
|
+
|
367
|
+
```rb
|
368
|
+
Rabarber.prune
|
369
|
+
```
|
370
|
+
|
339
371
|
### Context Migrations
|
340
372
|
|
341
373
|
Handle context changes when models are renamed or removed. These are irreversible data migrations.
|
342
374
|
|
343
375
|
```rb
|
344
|
-
# Rename a context class (e.g., when you rename your
|
345
|
-
migrate_authorization_context!("
|
376
|
+
# Rename a context class (e.g., when you rename your Ticket model to Task)
|
377
|
+
migrate_authorization_context!("Ticket", "Task")
|
346
378
|
|
347
|
-
# Remove orphaned context data (e.g., when you delete
|
348
|
-
delete_authorization_context!("
|
379
|
+
# Remove orphaned context data (e.g., when you delete the Ticket model entirely)
|
380
|
+
delete_authorization_context!("Ticket")
|
349
381
|
```
|
350
382
|
|
351
383
|
## View Helpers
|
@@ -373,22 +405,23 @@ Use conditional rendering based on roles:
|
|
373
405
|
</div>
|
374
406
|
<% end %>
|
375
407
|
|
376
|
-
<!-- With context -->
|
377
408
|
<%= visible_to(:owner, context: @project) do %>
|
378
|
-
<
|
409
|
+
<div class="project-owner-panel">
|
410
|
+
<!-- Content visible to project owners -->
|
411
|
+
</div>
|
379
412
|
<% end %>
|
380
413
|
```
|
381
414
|
|
382
415
|
## Getting Help and Contributing
|
383
416
|
|
384
417
|
### Getting Help
|
385
|
-
Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/
|
418
|
+
Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/enjaku4/rabarber/discussions) for:
|
386
419
|
- Usage questions
|
387
420
|
- Implementation guidance
|
388
421
|
- Feature suggestions
|
389
422
|
|
390
423
|
### Reporting Issues
|
391
|
-
Found a bug? Please [create an issue](https://github.com/
|
424
|
+
Found a bug? Please [create an issue](https://github.com/enjaku4/rabarber/issues) with:
|
392
425
|
- A clear description of the problem
|
393
426
|
- Steps to reproduce the issue
|
394
427
|
- Your environment details (Rails version, Ruby version, etc.)
|
@@ -397,21 +430,21 @@ Found a bug? Please [create an issue](https://github.com/brownboxdev/rabarber/is
|
|
397
430
|
Ready to contribute? You can:
|
398
431
|
- Fix bugs by submitting pull requests
|
399
432
|
- Improve documentation
|
400
|
-
- Add new features (please discuss first in our [discussions section](https://github.com/
|
433
|
+
- Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/rabarber/discussions))
|
401
434
|
|
402
|
-
Before contributing, please read the [contributing guidelines](https://github.com/
|
435
|
+
Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md)
|
403
436
|
|
404
437
|
## License
|
405
438
|
|
406
|
-
The gem is available as open source under the terms of the [MIT License](https://github.com/
|
439
|
+
The gem is available as open source under the terms of the [MIT License](https://github.com/enjaku4/rabarber/blob/main/LICENSE.txt).
|
407
440
|
|
408
441
|
## Code of Conduct
|
409
442
|
|
410
|
-
Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/
|
443
|
+
Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/enjaku4/rabarber/blob/main/CODE_OF_CONDUCT.md).
|
411
444
|
|
412
445
|
## Old Versions
|
413
446
|
|
414
447
|
Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
|
415
448
|
|
416
|
-
[v4.x.x](https://github.com/
|
417
|
-
| [v2.x.x](https://github.com/
|
449
|
+
[v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
|
450
|
+
| [v2.x.x](https://github.com/enjaku4/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/enjaku4/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
|
@@ -12,43 +12,39 @@ module Rabarber
|
|
12
12
|
default: true,
|
13
13
|
reader: true,
|
14
14
|
constructor: -> (value) do
|
15
|
-
Rabarber::Inputs.
|
15
|
+
Rabarber::Inputs::Boolean.new(
|
16
16
|
value,
|
17
|
-
as: :boolean,
|
18
17
|
error: Rabarber::ConfigurationError,
|
19
18
|
message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
|
20
|
-
)
|
19
|
+
).process
|
21
20
|
end
|
22
21
|
setting :current_user_method,
|
23
22
|
default: :current_user,
|
24
23
|
reader: true,
|
25
24
|
constructor: -> (value) do
|
26
|
-
Rabarber::Inputs.
|
25
|
+
Rabarber::Inputs::Symbol.new(
|
27
26
|
value,
|
28
|
-
as: :symbol,
|
29
27
|
error: Rabarber::ConfigurationError,
|
30
28
|
message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
|
31
|
-
)
|
29
|
+
).process
|
32
30
|
end
|
33
31
|
setting :user_model_name,
|
34
32
|
default: "User",
|
35
33
|
reader: true,
|
36
34
|
constructor: -> (value) do
|
37
|
-
Rabarber::Inputs.
|
35
|
+
Rabarber::Inputs::NonEmptyString.new(
|
38
36
|
value,
|
39
|
-
as: :string,
|
40
37
|
error: Rabarber::ConfigurationError,
|
41
38
|
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
|
42
|
-
)
|
39
|
+
).process
|
43
40
|
end
|
44
41
|
|
45
42
|
def user_model
|
46
|
-
Rabarber::Inputs.
|
43
|
+
Rabarber::Inputs::Model.new(
|
47
44
|
user_model_name,
|
48
|
-
as: :model,
|
49
45
|
error: Rabarber::ConfigurationError,
|
50
46
|
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{user_model_name.inspect}"
|
51
|
-
)
|
47
|
+
).process
|
52
48
|
end
|
53
49
|
end
|
54
50
|
end
|
@@ -19,36 +19,35 @@ module Rabarber
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def grant_access(action: nil, roles: nil, context: nil, if: nil, unless: nil)
|
22
|
+
if_rule = binding.local_variable_get(:if)
|
23
|
+
unless_rule = binding.local_variable_get(:unless)
|
24
|
+
|
22
25
|
Rabarber::Core::Permissions.add(
|
23
26
|
self,
|
24
|
-
Rabarber::Inputs.
|
27
|
+
Rabarber::Inputs::Symbol.new(
|
25
28
|
action,
|
26
|
-
as: :symbol,
|
27
29
|
optional: true,
|
28
30
|
message: "Expected a symbol or a string, got #{action.inspect}"
|
29
|
-
),
|
30
|
-
Rabarber::Inputs.
|
31
|
+
).process,
|
32
|
+
Rabarber::Inputs::Roles.new(
|
31
33
|
roles,
|
32
|
-
as: :roles,
|
33
34
|
message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{roles.inspect}"
|
34
|
-
),
|
35
|
-
Rabarber::Inputs.
|
35
|
+
).process,
|
36
|
+
Rabarber::Inputs::Contexts::Authorizational.new(
|
36
37
|
context,
|
37
|
-
|
38
|
+
error: Rabarber::InvalidContextError,
|
38
39
|
message: "Expected a Class, an instance of ActiveRecord model, a symbol, a string, or a proc, got #{context.inspect}"
|
39
|
-
),
|
40
|
-
Rabarber::Inputs.
|
41
|
-
|
42
|
-
as: :dynamic_rule,
|
40
|
+
).resolve,
|
41
|
+
Rabarber::Inputs::DynamicRule.new(
|
42
|
+
if_rule,
|
43
43
|
optional: true,
|
44
|
-
message: "Expected a symbol, a string, or a proc, got #{
|
45
|
-
),
|
46
|
-
Rabarber::Inputs.
|
47
|
-
|
48
|
-
as: :dynamic_rule,
|
44
|
+
message: "Expected a symbol, a string, or a proc, got #{if_rule.inspect}"
|
45
|
+
).process,
|
46
|
+
Rabarber::Inputs::DynamicRule.new(
|
47
|
+
unless_rule,
|
49
48
|
optional: true,
|
50
|
-
message: "Expected a symbol, a string, or a proc, got #{
|
51
|
-
)
|
49
|
+
message: "Expected a symbol, a string, or a proc, got #{unless_rule.inspect}"
|
50
|
+
).process
|
52
51
|
)
|
53
52
|
end
|
54
53
|
end
|
@@ -22,7 +22,7 @@ module Rabarber
|
|
22
22
|
class << self
|
23
23
|
def add(controller, action, roles, context, dynamic_rule, negated_dynamic_rule)
|
24
24
|
rule = Rabarber::Core::Rule.new(roles, context, dynamic_rule, negated_dynamic_rule)
|
25
|
-
action ? action_rules[controller][action]
|
25
|
+
action ? action_rules[controller][action] << rule : controller_rules[controller] << rule
|
26
26
|
end
|
27
27
|
|
28
28
|
def controller_rules
|
data/lib/rabarber/core/rule.rb
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Core
|
5
5
|
class Rule
|
6
|
-
attr_reader :roles, :context, :dynamic_rule, :negated_dynamic_rule
|
7
|
-
|
8
6
|
DEFAULT_DYNAMIC_RULE = -> { true }.freeze
|
9
7
|
DEFAULT_NEGATED_DYNAMIC_RULE = -> { false }.freeze
|
10
8
|
private_constant :DEFAULT_DYNAMIC_RULE, :DEFAULT_NEGATED_DYNAMIC_RULE
|
@@ -21,11 +19,11 @@ module Rabarber
|
|
21
19
|
end
|
22
20
|
|
23
21
|
def roles_permitted?(roleable, controller_instance)
|
24
|
-
roles.empty? || roleable.has_role?(
|
22
|
+
@roles.empty? || roleable.has_role?(*@roles, context: resolve_context(controller_instance))
|
25
23
|
end
|
26
24
|
|
27
25
|
def dynamic_rules_followed?(controller_instance)
|
28
|
-
execute_rule(controller_instance, dynamic_rule) && !execute_rule(controller_instance, negated_dynamic_rule)
|
26
|
+
execute_rule(controller_instance, @dynamic_rule) && !execute_rule(controller_instance, @negated_dynamic_rule)
|
29
27
|
end
|
30
28
|
|
31
29
|
private
|
@@ -35,12 +33,17 @@ module Rabarber
|
|
35
33
|
end
|
36
34
|
|
37
35
|
def resolve_context(controller_instance)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
context = case @context
|
37
|
+
when Proc then controller_instance.instance_exec(&@context)
|
38
|
+
when Symbol then controller_instance.send(@context)
|
39
|
+
else @context
|
40
|
+
end
|
41
|
+
|
42
|
+
Rabarber::Inputs::Context.new(
|
43
|
+
context,
|
44
|
+
error: Rabarber::InvalidContextError,
|
45
|
+
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
46
|
+
).resolve
|
44
47
|
end
|
45
48
|
end
|
46
49
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-types"
|
4
|
+
|
5
|
+
module Rabarber
|
6
|
+
module Inputs
|
7
|
+
class Base
|
8
|
+
include Dry.Types()
|
9
|
+
|
10
|
+
def initialize(value, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
|
11
|
+
@value = value
|
12
|
+
@optional = optional
|
13
|
+
@error = error
|
14
|
+
@message = message
|
15
|
+
end
|
16
|
+
|
17
|
+
def process
|
18
|
+
type_checker = @optional ? type.optional : type
|
19
|
+
type_checker[@value]
|
20
|
+
rescue Dry::Types::CoercionError
|
21
|
+
raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def type
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def raise_error
|
31
|
+
raise @error, @message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Inputs
|
5
|
+
class Context < Rabarber::Inputs::Base
|
6
|
+
def resolve
|
7
|
+
case context = process
|
8
|
+
when nil
|
9
|
+
{ context_type: nil, context_id: nil }
|
10
|
+
when Class
|
11
|
+
{ context_type: context.to_s, context_id: nil }
|
12
|
+
when ActiveRecord::Base
|
13
|
+
raise_error unless context.persisted?
|
14
|
+
{ context_type: context.class.to_s, context_id: context.public_send(context.class.primary_key) }
|
15
|
+
else
|
16
|
+
context
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def type
|
23
|
+
self.class::Strict::Class |
|
24
|
+
self.class::Instance(ActiveRecord::Base) |
|
25
|
+
self.class::Hash.schema(
|
26
|
+
context_type: self.class::Strict::String | self.class::Nil,
|
27
|
+
context_id: self.class::Strict::String | self.class::Strict::Integer | self.class::Nil
|
28
|
+
) |
|
29
|
+
self.class::Nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Inputs
|
5
|
+
module Contexts
|
6
|
+
class Authorizational < Rabarber::Inputs::Context
|
7
|
+
private
|
8
|
+
|
9
|
+
def type = self.class::Coercible::Symbol.constrained(min_size: 1) | self.class::Instance(Proc) | super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Inputs
|
5
|
+
class Roles < Rabarber::Inputs::Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def type
|
9
|
+
self.class::Array.of(self.class::Coercible::Symbol.constrained(min_size: 1, format: /\A[a-z0-9_]+\z/)).constructor { Kernel::Array(_1) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,17 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Rabarber
|
4
|
-
module
|
4
|
+
module Roleable
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
raise Rabarber::Error, "Rabarber::
|
8
|
+
raise Rabarber::Error, "Rabarber::Roleable can only be included once" if defined?(@@included) && @@included != name
|
9
9
|
|
10
10
|
@@included = name
|
11
11
|
|
12
12
|
has_and_belongs_to_many :rabarber_roles, class_name: "Rabarber::Role",
|
13
13
|
foreign_key: "roleable_id",
|
14
14
|
join_table: "rabarber_roles_roleables"
|
15
|
+
|
16
|
+
scope :with_role, -> (*role_names, context: nil) {
|
17
|
+
joins(:rabarber_roles).where(
|
18
|
+
rabarber_roles: { name: process_role_names(role_names), **process_context(context) }
|
19
|
+
).distinct
|
20
|
+
}
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def process_role_names(role_names)
|
24
|
+
Rabarber::Inputs::Roles.new(
|
25
|
+
role_names,
|
26
|
+
message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{role_names.inspect}"
|
27
|
+
).process
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_context(context)
|
31
|
+
Rabarber::Inputs::Context.new(
|
32
|
+
context,
|
33
|
+
error: Rabarber::InvalidContextError,
|
34
|
+
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
35
|
+
).resolve
|
36
|
+
end
|
37
|
+
end
|
38
|
+
delegate :process_role_names, :process_context, to: :class, private: true
|
15
39
|
end
|
16
40
|
|
17
41
|
def roles(context: nil)
|
@@ -64,13 +88,15 @@ module Rabarber
|
|
64
88
|
end
|
65
89
|
|
66
90
|
def revoke_all_roles
|
67
|
-
return if rabarber_roles.none?
|
91
|
+
return [] if rabarber_roles.none?
|
68
92
|
|
69
93
|
contexts = all_roles.keys.map { process_context(_1) }
|
70
94
|
|
71
95
|
rabarber_roles.clear
|
72
96
|
|
73
97
|
delete_roleable_cache(contexts:)
|
98
|
+
|
99
|
+
[]
|
74
100
|
end
|
75
101
|
|
76
102
|
private
|
@@ -80,22 +106,6 @@ module Rabarber
|
|
80
106
|
new_roles.each { |role_name| Rabarber::Role.create!(name: role_name, **context) }
|
81
107
|
end
|
82
108
|
|
83
|
-
def process_role_names(role_names)
|
84
|
-
Rabarber::Inputs.process(
|
85
|
-
role_names,
|
86
|
-
as: :roles,
|
87
|
-
message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{role_names.inspect}"
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
|
-
def process_context(context)
|
92
|
-
Rabarber::Inputs.process(
|
93
|
-
context,
|
94
|
-
as: :role_context,
|
95
|
-
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
109
|
def delete_roleable_cache(contexts:)
|
100
110
|
Rabarber::Core::Cache.delete(*contexts.map { [roleable_id, _1] }, [roleable_id, :all])
|
101
111
|
end
|
data/lib/rabarber/models/role.rb
CHANGED
@@ -12,10 +12,14 @@ module Rabarber
|
|
12
12
|
|
13
13
|
class << self
|
14
14
|
def names(context: nil)
|
15
|
+
deprecation_warning("names", "Rabarber.roles")
|
16
|
+
|
15
17
|
where(process_context(context)).pluck(:name).map(&:to_sym)
|
16
18
|
end
|
17
19
|
|
18
20
|
def all_names
|
21
|
+
deprecation_warning("all_names", "Rabarber.all_roles")
|
22
|
+
|
19
23
|
includes(:context).each_with_object({}) do |role, hash|
|
20
24
|
(hash[role.context] ||= []) << role.name.to_sym
|
21
25
|
rescue ActiveRecord::RecordNotFound
|
@@ -26,6 +30,8 @@ module Rabarber
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def add(name, context: nil)
|
33
|
+
deprecation_warning("add", "Rabarber.create_role")
|
34
|
+
|
29
35
|
name = process_role_name(name)
|
30
36
|
processed_context = process_context(context)
|
31
37
|
|
@@ -35,6 +41,8 @@ module Rabarber
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def rename(old_name, new_name, context: nil, force: false)
|
44
|
+
deprecation_warning("rename", "Rabarber.rename_role")
|
45
|
+
|
38
46
|
processed_context = process_context(context)
|
39
47
|
role = find_by(name: process_role_name(old_name), **processed_context)
|
40
48
|
|
@@ -50,6 +58,8 @@ module Rabarber
|
|
50
58
|
end
|
51
59
|
|
52
60
|
def remove(name, context: nil, force: false)
|
61
|
+
deprecation_warning("remove", "Rabarber.delete_role")
|
62
|
+
|
53
63
|
processed_context = process_context(context)
|
54
64
|
role = find_by(name: process_role_name(name), **processed_context)
|
55
65
|
|
@@ -63,31 +73,53 @@ module Rabarber
|
|
63
73
|
end
|
64
74
|
|
65
75
|
def assignees(name, context: nil)
|
76
|
+
deprecation_warning("assignees", "#{Rabarber::Configuration.user_model_name}.with_role")
|
77
|
+
|
66
78
|
find_by(name: process_role_name(name), **process_context(context))&.roleables || Rabarber::Configuration.user_model.none
|
67
79
|
end
|
68
80
|
|
81
|
+
def prune
|
82
|
+
orphaned_roles = where.not(context_id: nil).includes(:context).filter_map do |role|
|
83
|
+
!role.context
|
84
|
+
rescue ActiveRecord::RecordNotFound
|
85
|
+
role.id
|
86
|
+
end
|
87
|
+
|
88
|
+
return if orphaned_roles.empty?
|
89
|
+
|
90
|
+
ActiveRecord::Base.transaction do
|
91
|
+
ActiveRecord::Base.connection.execute(
|
92
|
+
ActiveRecord::Base.sanitize_sql(
|
93
|
+
["DELETE FROM rabarber_roles_roleables WHERE role_id IN (?)", orphaned_roles]
|
94
|
+
)
|
95
|
+
)
|
96
|
+
where(id: orphaned_roles).delete_all
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
69
100
|
private
|
70
101
|
|
102
|
+
def deprecation_warning(method, alternative)
|
103
|
+
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.") if caller_locations.map(&:label).exclude?(alternative)
|
104
|
+
end
|
105
|
+
|
71
106
|
def delete_roleables_cache(role, context:)
|
72
|
-
role.roleables.
|
73
|
-
Rabarber::Core::Cache.delete(*batch.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
|
74
|
-
end
|
107
|
+
Rabarber::Core::Cache.delete(*role.roleables.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
|
75
108
|
end
|
76
109
|
|
77
110
|
def process_role_name(name)
|
78
|
-
Rabarber::Inputs.
|
111
|
+
Rabarber::Inputs::Role.new(
|
79
112
|
name,
|
80
|
-
as: :role,
|
81
113
|
message: "Expected a symbol or a string containing only lowercase letters, numbers, and underscores, got #{name.inspect}"
|
82
|
-
)
|
114
|
+
).process
|
83
115
|
end
|
84
116
|
|
85
117
|
def process_context(context)
|
86
|
-
Rabarber::Inputs.
|
118
|
+
Rabarber::Inputs::Context.new(
|
87
119
|
context,
|
88
|
-
|
120
|
+
error: Rabarber::InvalidContextError,
|
89
121
|
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
90
|
-
)
|
122
|
+
).resolve
|
91
123
|
end
|
92
124
|
end
|
93
125
|
|
data/lib/rabarber/railtie.rb
CHANGED
@@ -6,26 +6,18 @@ module Rabarber
|
|
6
6
|
class Railtie < Rails::Railtie
|
7
7
|
initializer "rabarber.to_prepare" do |app|
|
8
8
|
app.config.to_prepare do
|
9
|
-
|
10
|
-
Rabarber::
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def check_integrity
|
17
|
-
Rabarber::Core::IntegrityChecker.run!
|
18
|
-
end
|
9
|
+
if ActiveRecord::Base.connection.data_source_exists?("rabarber_roles")
|
10
|
+
Rabarber::Role.where.not(context_type: nil).distinct.pluck(:context_type).each do |context_class|
|
11
|
+
context_class.constantize
|
12
|
+
rescue NameError => e
|
13
|
+
raise Rabarber::Error, "Context not found: class `#{e.name}` may have been renamed or deleted"
|
19
14
|
end
|
20
15
|
end
|
21
|
-
user_model = Rabarber::Configuration.user_model
|
22
|
-
user_model.include Rabarber::HasRoles unless user_model < Rabarber::HasRoles
|
23
|
-
end
|
24
|
-
end
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
Rabarber::
|
17
|
+
Rabarber::Core::Permissions.reset! unless app.config.eager_load
|
18
|
+
|
19
|
+
user_model = Rabarber::Configuration.user_model
|
20
|
+
user_model.include Rabarber::Roleable unless user_model < Rabarber::Roleable
|
29
21
|
end
|
30
22
|
end
|
31
23
|
|
data/lib/rabarber/version.rb
CHANGED
data/lib/rabarber.rb
CHANGED
@@ -5,18 +5,37 @@ require_relative "rabarber/version"
|
|
5
5
|
require "active_record"
|
6
6
|
require "active_support"
|
7
7
|
|
8
|
-
require_relative "rabarber/inputs"
|
8
|
+
require_relative "rabarber/inputs/base"
|
9
|
+
require_relative "rabarber/inputs/boolean"
|
10
|
+
require_relative "rabarber/inputs/context"
|
11
|
+
require_relative "rabarber/inputs/contexts/authorizational"
|
12
|
+
require_relative "rabarber/inputs/dynamic_rule"
|
13
|
+
require_relative "rabarber/inputs/model"
|
14
|
+
require_relative "rabarber/inputs/non_empty_string"
|
15
|
+
require_relative "rabarber/inputs/role"
|
16
|
+
require_relative "rabarber/inputs/roles"
|
17
|
+
require_relative "rabarber/inputs/symbol"
|
9
18
|
|
10
19
|
require_relative "rabarber/configuration"
|
11
20
|
|
12
21
|
module Rabarber
|
13
22
|
class Error < StandardError; end
|
14
|
-
class ConfigurationError < Rabarber::Error; end
|
15
23
|
class InvalidArgumentError < Rabarber::Error; end
|
24
|
+
class ConfigurationError < Rabarber::InvalidArgumentError; end
|
25
|
+
class InvalidContextError < Rabarber::InvalidArgumentError; end
|
16
26
|
class NotFoundError < Rabarber::Error; end
|
17
27
|
|
18
28
|
delegate :configure, to: Rabarber::Configuration
|
19
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
|
20
39
|
end
|
21
40
|
|
22
41
|
require_relative "rabarber/core/cache"
|
@@ -26,10 +45,9 @@ require_relative "rabarber/core/roleable"
|
|
26
45
|
require_relative "rabarber/controllers/concerns/authorization"
|
27
46
|
require_relative "rabarber/helpers/helpers"
|
28
47
|
require_relative "rabarber/helpers/migration_helpers"
|
29
|
-
require_relative "rabarber/models/concerns/
|
48
|
+
require_relative "rabarber/models/concerns/roleable"
|
30
49
|
require_relative "rabarber/models/role"
|
31
50
|
|
32
51
|
require_relative "rabarber/core/permissions"
|
33
|
-
require_relative "rabarber/core/integrity_checker"
|
34
52
|
|
35
53
|
require_relative "rabarber/railtie"
|
data/rabarber.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "rabarber"
|
7
7
|
spec.version = Rabarber::VERSION
|
8
8
|
spec.authors = ["enjaku4", "trafium"]
|
9
|
-
spec.homepage = "https://github.com/
|
9
|
+
spec.homepage = "https://github.com/enjaku4/rabarber"
|
10
10
|
spec.metadata["homepage_uri"] = spec.homepage
|
11
11
|
spec.metadata["source_code_uri"] = spec.homepage
|
12
12
|
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
|
-
spec.add_dependency "dry-configurable", "~> 1.
|
25
|
-
spec.add_dependency "dry-types", "~> 1.
|
24
|
+
spec.add_dependency "dry-configurable", "~> 1.1"
|
25
|
+
spec.add_dependency "dry-types", "~> 1.7"
|
26
26
|
spec.add_dependency "rails", ">= 7.1", "< 8.1"
|
27
27
|
end
|
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: 5.
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- enjaku4
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dry-types
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.7'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rails
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,25 +72,33 @@ files:
|
|
72
72
|
- lib/rabarber/controllers/concerns/authorization.rb
|
73
73
|
- lib/rabarber/core/access.rb
|
74
74
|
- lib/rabarber/core/cache.rb
|
75
|
-
- lib/rabarber/core/integrity_checker.rb
|
76
75
|
- lib/rabarber/core/permissions.rb
|
77
76
|
- lib/rabarber/core/roleable.rb
|
78
77
|
- lib/rabarber/core/rule.rb
|
79
78
|
- lib/rabarber/helpers/helpers.rb
|
80
79
|
- lib/rabarber/helpers/migration_helpers.rb
|
81
|
-
- lib/rabarber/inputs.rb
|
82
|
-
- lib/rabarber/
|
80
|
+
- lib/rabarber/inputs/base.rb
|
81
|
+
- lib/rabarber/inputs/boolean.rb
|
82
|
+
- lib/rabarber/inputs/context.rb
|
83
|
+
- lib/rabarber/inputs/contexts/authorizational.rb
|
84
|
+
- lib/rabarber/inputs/dynamic_rule.rb
|
85
|
+
- lib/rabarber/inputs/model.rb
|
86
|
+
- lib/rabarber/inputs/non_empty_string.rb
|
87
|
+
- lib/rabarber/inputs/role.rb
|
88
|
+
- lib/rabarber/inputs/roles.rb
|
89
|
+
- lib/rabarber/inputs/symbol.rb
|
90
|
+
- lib/rabarber/models/concerns/roleable.rb
|
83
91
|
- lib/rabarber/models/role.rb
|
84
92
|
- lib/rabarber/railtie.rb
|
85
93
|
- lib/rabarber/version.rb
|
86
94
|
- rabarber.gemspec
|
87
|
-
homepage: https://github.com/
|
95
|
+
homepage: https://github.com/enjaku4/rabarber
|
88
96
|
licenses:
|
89
97
|
- MIT
|
90
98
|
metadata:
|
91
|
-
homepage_uri: https://github.com/
|
92
|
-
source_code_uri: https://github.com/
|
93
|
-
changelog_uri: https://github.com/
|
99
|
+
homepage_uri: https://github.com/enjaku4/rabarber
|
100
|
+
source_code_uri: https://github.com/enjaku4/rabarber
|
101
|
+
changelog_uri: https://github.com/enjaku4/rabarber/blob/main/CHANGELOG.md
|
94
102
|
rubygems_mfa_required: 'true'
|
95
103
|
rdoc_options: []
|
96
104
|
require_paths:
|
@@ -109,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
117
|
- !ruby/object:Gem::Version
|
110
118
|
version: '0'
|
111
119
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
120
|
+
rubygems_version: 3.7.1
|
113
121
|
specification_version: 4
|
114
122
|
summary: Simple role-based authorization library for Ruby on Rails
|
115
123
|
test_files: []
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Core
|
5
|
-
module IntegrityChecker
|
6
|
-
extend self
|
7
|
-
|
8
|
-
def run!
|
9
|
-
check_for_missing_class_context
|
10
|
-
prune_missing_instance_context
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def check_for_missing_class_context
|
16
|
-
Rabarber::Role.where.not(context_type: nil).distinct.pluck(:context_type).each do |context_class|
|
17
|
-
context_class.constantize
|
18
|
-
rescue NameError => e
|
19
|
-
raise Rabarber::Error, "Context not found: class #{e.name} may have been renamed or deleted"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def prune_missing_instance_context
|
24
|
-
ids = Rabarber::Role.where.not(context_id: nil).includes(:context).filter_map do |role|
|
25
|
-
role.context
|
26
|
-
nil
|
27
|
-
rescue ActiveRecord::RecordNotFound
|
28
|
-
role.id
|
29
|
-
end
|
30
|
-
|
31
|
-
return if ids.empty?
|
32
|
-
|
33
|
-
ActiveRecord::Base.transaction do
|
34
|
-
ActiveRecord::Base.connection.execute(
|
35
|
-
ActiveRecord::Base.sanitize_sql(
|
36
|
-
["DELETE FROM rabarber_roles_roleables WHERE role_id IN (?)", ids]
|
37
|
-
)
|
38
|
-
)
|
39
|
-
Rabarber::Role.where(id: ids).delete_all
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/lib/rabarber/inputs.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dry-types"
|
4
|
-
|
5
|
-
module Rabarber
|
6
|
-
module Inputs
|
7
|
-
extend self
|
8
|
-
|
9
|
-
include Dry.Types()
|
10
|
-
|
11
|
-
PROC_TYPE = self::Instance(Proc)
|
12
|
-
SYMBOL_TYPE = self::Coercible::Symbol.constrained(min_size: 1)
|
13
|
-
ROLE_TYPE = self::SYMBOL_TYPE.constrained(format: /\A[a-z0-9_]+\z/)
|
14
|
-
|
15
|
-
CONTEXT_TYPE = self::Strict::Class | self::Instance(ActiveRecord::Base) | self::Hash.schema(
|
16
|
-
context_type: self::Strict::String | self::Nil,
|
17
|
-
context_id: self::Strict::String | self::Strict::Integer | self::Nil
|
18
|
-
) | self::Nil
|
19
|
-
|
20
|
-
TYPES = {
|
21
|
-
boolean: self::Strict::Bool,
|
22
|
-
string: self::Strict::String.constrained(min_size: 1).constructor { _1.is_a?(::String) ? _1.strip : _1 },
|
23
|
-
symbol: self::SYMBOL_TYPE,
|
24
|
-
role: self::ROLE_TYPE,
|
25
|
-
roles: self::Array.of(self::ROLE_TYPE).constructor { Kernel::Array(_1) },
|
26
|
-
model: self::Strict::Class.constructor { _1.try(:safe_constantize) }.constrained(lt: ActiveRecord::Base),
|
27
|
-
dynamic_rule: self::SYMBOL_TYPE | self::PROC_TYPE,
|
28
|
-
role_context: self::CONTEXT_TYPE,
|
29
|
-
authorization_context: self::SYMBOL_TYPE | self::PROC_TYPE | self::CONTEXT_TYPE
|
30
|
-
}.freeze
|
31
|
-
|
32
|
-
def process(value, as:, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
|
33
|
-
checker = type_for(as)
|
34
|
-
checker = checker.optional if optional
|
35
|
-
|
36
|
-
result = checker[value]
|
37
|
-
|
38
|
-
[:role_context, :authorization_context].include?(as) ? resolve_context(result) : result
|
39
|
-
rescue Dry::Types::CoercionError => e
|
40
|
-
raise error, message || e.message
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def type_for(name) = self::TYPES.fetch(name)
|
46
|
-
|
47
|
-
def resolve_context(value)
|
48
|
-
case value
|
49
|
-
when nil
|
50
|
-
{ context_type: nil, context_id: nil }
|
51
|
-
when Class
|
52
|
-
{ context_type: value.to_s, context_id: nil }
|
53
|
-
when ActiveRecord::Base
|
54
|
-
raise Dry::Types::CoercionError, "instance context not persisted" unless value.persisted?
|
55
|
-
|
56
|
-
{ context_type: value.class.to_s, context_id: value.public_send(value.class.primary_key) }
|
57
|
-
else
|
58
|
-
value
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|