rabarber 5.1.0 → 5.1.2
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 +14 -0
- data/README.md +60 -41
- data/lib/rabarber/configuration.rb +38 -24
- data/lib/rabarber/controllers/concerns/authorization.rb +27 -5
- data/lib/rabarber/core/cache.rb +1 -1
- data/lib/rabarber/core/permissions.rb +1 -1
- data/lib/rabarber/core/roleable.rb +3 -3
- data/lib/rabarber/core/rule.rb +13 -9
- 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 +9 -2
- data/lib/rabarber/models/role.rb +12 -8
- data/lib/rabarber/railtie.rb +1 -1
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +23 -24
- data/rabarber.gemspec +2 -0
- metadata +39 -12
- data/lib/rabarber/input/action.rb +0 -21
- data/lib/rabarber/input/ar_model.rb +0 -23
- data/lib/rabarber/input/authorization_context.rb +0 -25
- data/lib/rabarber/input/base.rb +0 -37
- data/lib/rabarber/input/context.rb +0 -33
- data/lib/rabarber/input/dynamic_rule.rb +0 -21
- data/lib/rabarber/input/role.rb +0 -23
- data/lib/rabarber/input/roles.rb +0 -25
- data/lib/rabarber/input/types/boolean.rb +0 -23
- data/lib/rabarber/input/types/proc.rb +0 -23
- data/lib/rabarber/input/types/symbol.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcf3063fc3e0bb700ae4e372cec9807b3796863f8e94e5b2f8e79d9594d50b10
|
4
|
+
data.tar.gz: 82abf396141f483ca0ec10982bb9ef35ea8a1d79f0a1e111f2c8b483a0e480e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 023fddca28a49a89534c1d5c0b716e7f518a3a975e2b6a5883f156f75acd73a28564b475080fd5abdf7ffce294b21c1af07be276141ac747d92b9087b8cbac96
|
7
|
+
data.tar.gz: 7ae10c74e73ee844d22011a6678a8b9af579d2657d3020fe28958912c3b4a1fb033ed0a11c7af126c7367e30fcd54a1058f529b39acf0cac31391f5bc0a76146
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## v5.1.2
|
2
|
+
|
3
|
+
### Misc:
|
4
|
+
|
5
|
+
- Minor performance improvements and code cleanup
|
6
|
+
- Relaxed dependency versions
|
7
|
+
|
8
|
+
## v5.1.1
|
9
|
+
|
10
|
+
### Misc:
|
11
|
+
|
12
|
+
- Switched to `dry-types` for input validation
|
13
|
+
- Switched to `dry-configurable` for configuration management
|
14
|
+
|
1
15
|
## v5.1.0
|
2
16
|
|
3
17
|
### Features:
|
data/README.md
CHANGED
@@ -18,8 +18,8 @@ Rabarber is a role-based authorization library for Ruby on Rails that focuses on
|
|
18
18
|
**Gem Usage:**
|
19
19
|
- [Installation](#installation)
|
20
20
|
- [Configuration](#configuration)
|
21
|
-
- [Role Management](#role-management)
|
22
21
|
- [User Role Methods](#user-role-methods)
|
22
|
+
- [Role Management](#role-management)
|
23
23
|
- [Controller Authorization](#controller-authorization)
|
24
24
|
- [Dynamic Rules](#dynamic-rules)
|
25
25
|
- [Multi-tenancy / Context](#multi-tenancy--context)
|
@@ -27,7 +27,7 @@ Rabarber is a role-based authorization library for Ruby on Rails that focuses on
|
|
27
27
|
|
28
28
|
|
29
29
|
**Community Resources:**
|
30
|
-
- [Contributing](#contributing)
|
30
|
+
- [Getting Help and Contributing](#getting-help-and-contributing)
|
31
31
|
- [License](#license)
|
32
32
|
- [Code of Conduct](#code-of-conduct)
|
33
33
|
- [Old Versions](#old-versions)
|
@@ -80,30 +80,6 @@ To clear the role cache manually:
|
|
80
80
|
Rabarber::Cache.clear
|
81
81
|
```
|
82
82
|
|
83
|
-
## Role Management
|
84
|
-
|
85
|
-
### Direct Role Operations
|
86
|
-
|
87
|
-
```rb
|
88
|
-
# Create a new role
|
89
|
-
Rabarber::Role.add(:admin)
|
90
|
-
|
91
|
-
# Rename a role
|
92
|
-
Rabarber::Role.rename(:admin, :administrator)
|
93
|
-
Rabarber::Role.rename(:admin, :administrator, force: true) # Force if role is assigned to users
|
94
|
-
|
95
|
-
# Remove a role
|
96
|
-
Rabarber::Role.remove(:admin)
|
97
|
-
Rabarber::Role.remove(:admin, force: true) # Force if role is assigned to users
|
98
|
-
|
99
|
-
# List available roles
|
100
|
-
Rabarber::Role.names
|
101
|
-
Rabarber::Role.all_names # All roles grouped by context
|
102
|
-
|
103
|
-
# Get users assigned to a role
|
104
|
-
Rabarber::Role.assignees(:admin)
|
105
|
-
```
|
106
|
-
|
107
83
|
## User Role Methods
|
108
84
|
|
109
85
|
Your user model is automatically augmented with role management methods:
|
@@ -137,6 +113,30 @@ user.roles
|
|
137
113
|
user.all_roles
|
138
114
|
```
|
139
115
|
|
116
|
+
## Role Management
|
117
|
+
|
118
|
+
### Direct Role Operations
|
119
|
+
|
120
|
+
```rb
|
121
|
+
# Create a new role
|
122
|
+
Rabarber::Role.add(:admin)
|
123
|
+
|
124
|
+
# Rename a role
|
125
|
+
Rabarber::Role.rename(:admin, :administrator)
|
126
|
+
Rabarber::Role.rename(:admin, :administrator, force: true) # Force if role is assigned to users
|
127
|
+
|
128
|
+
# Remove a role
|
129
|
+
Rabarber::Role.remove(:admin)
|
130
|
+
Rabarber::Role.remove(:admin, force: true) # Force if role is assigned to users
|
131
|
+
|
132
|
+
# List available roles
|
133
|
+
Rabarber::Role.names
|
134
|
+
Rabarber::Role.all_names # All roles grouped by context
|
135
|
+
|
136
|
+
# Get users assigned to a role
|
137
|
+
Rabarber::Role.assignees(:admin)
|
138
|
+
```
|
139
|
+
|
140
140
|
## Controller Authorization
|
141
141
|
|
142
142
|
### Basic Setup
|
@@ -181,11 +181,6 @@ class TicketsController < ApplicationController
|
|
181
181
|
def index
|
182
182
|
# Accessible to admin, manager, and support roles
|
183
183
|
end
|
184
|
-
|
185
|
-
grant_access action: :destroy, roles: :admin
|
186
|
-
def destroy
|
187
|
-
# Accessible to admin role only
|
188
|
-
end
|
189
184
|
end
|
190
185
|
```
|
191
186
|
|
@@ -203,7 +198,9 @@ class InvoicesController < BaseController
|
|
203
198
|
|
204
199
|
grant_access action: :index, roles: :manager
|
205
200
|
grant_access action: :index, roles: :supervisor
|
206
|
-
|
201
|
+
def index
|
202
|
+
# Index is accessible to admin, accountant, manager, and supervisor
|
203
|
+
end
|
207
204
|
end
|
208
205
|
```
|
209
206
|
|
@@ -218,7 +215,14 @@ end
|
|
218
215
|
|
219
216
|
class MixedController < ApplicationController
|
220
217
|
grant_access action: :index # Unrestricted index action
|
218
|
+
def index
|
219
|
+
# Accessible to all users
|
220
|
+
end
|
221
|
+
|
221
222
|
grant_access action: :show, roles: :member # Restricted show action
|
223
|
+
def show
|
224
|
+
# Accessible to members only
|
225
|
+
end
|
222
226
|
end
|
223
227
|
```
|
224
228
|
|
@@ -235,13 +239,13 @@ class ApplicationController < ActionController::Base
|
|
235
239
|
private
|
236
240
|
|
237
241
|
def when_unauthorized
|
238
|
-
#
|
239
|
-
# Custom behavior example:
|
240
|
-
head :not_found # Hide existence of protected resources
|
242
|
+
head :not_found # Custom behavior to hide existence of protected resources
|
241
243
|
end
|
242
244
|
end
|
243
245
|
```
|
244
246
|
|
247
|
+
By default, Rabarber will redirect back (HTML format) or return 403 (other formats).
|
248
|
+
|
245
249
|
## Dynamic Rules
|
246
250
|
|
247
251
|
Add conditional logic to authorization rules:
|
@@ -253,9 +257,15 @@ class OrdersController < ApplicationController
|
|
253
257
|
|
254
258
|
# Proc-based conditions
|
255
259
|
grant_access action: :show, roles: :client, if: -> { current_user.company_id == Order.find(params[:id]).company_id }
|
260
|
+
def show
|
261
|
+
# Accessible to company managers unless suspended, and to clients if the client's company matches the order's company
|
262
|
+
end
|
256
263
|
|
257
264
|
# Dynamic-only rules (no roles required, can be used with custom policies)
|
258
265
|
grant_access action: :index, if: -> { OrdersPolicy.new(current_user).can_access?(:index) }
|
266
|
+
def index
|
267
|
+
# Accessible to company managers unless suspended, and to users based on custom policy logic
|
268
|
+
end
|
259
269
|
|
260
270
|
private
|
261
271
|
|
@@ -271,7 +281,7 @@ end
|
|
271
281
|
|
272
282
|
## Multi-tenancy / Context
|
273
283
|
|
274
|
-
All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes
|
284
|
+
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.
|
275
285
|
|
276
286
|
### Contextual Role Assignment
|
277
287
|
|
@@ -287,6 +297,9 @@ user.assign_roles(:admin, context: Project)
|
|
287
297
|
user.has_role?(:owner, context: project)
|
288
298
|
user.has_role?(:admin, context: Project)
|
289
299
|
|
300
|
+
# Revoke roles within a specific context
|
301
|
+
user.revoke_roles(:owner, context: project)
|
302
|
+
|
290
303
|
# Get roles within context
|
291
304
|
user.roles(context: project)
|
292
305
|
Rabarber::Role.names(context: Project)
|
@@ -301,9 +314,15 @@ class ProjectsController < ApplicationController
|
|
301
314
|
|
302
315
|
# Instance-based context (method)
|
303
316
|
grant_access action: :show, roles: :member, context: :current_project
|
317
|
+
def show
|
318
|
+
# Accessible to Project admin and members of the current project
|
319
|
+
end
|
304
320
|
|
305
321
|
# Instance-based context (proc)
|
306
322
|
grant_access action: :update, roles: :owner, context: -> { Project.find(params[:id]) }
|
323
|
+
def update
|
324
|
+
# Accessible to Project admin and owner of the current project
|
325
|
+
end
|
307
326
|
|
308
327
|
private
|
309
328
|
|
@@ -318,11 +337,11 @@ end
|
|
318
337
|
Handle context changes when models are renamed or removed. These are irreversible data migrations.
|
319
338
|
|
320
339
|
```rb
|
321
|
-
# Rename a context class (e.g., when you rename your
|
322
|
-
migrate_authorization_context!("
|
340
|
+
# Rename a context class (e.g., when you rename your Ticket model to Task)
|
341
|
+
migrate_authorization_context!("Ticket", "Task")
|
323
342
|
|
324
|
-
# Remove orphaned context data (e.g., when you delete
|
325
|
-
delete_authorization_context!("
|
343
|
+
# Remove orphaned context data (e.g., when you delete the Ticket model entirely)
|
344
|
+
delete_authorization_context!("Ticket")
|
326
345
|
```
|
327
346
|
|
328
347
|
## View Helpers
|
@@ -356,7 +375,7 @@ Use conditional rendering based on roles:
|
|
356
375
|
<% end %>
|
357
376
|
```
|
358
377
|
|
359
|
-
## Contributing
|
378
|
+
## Getting Help and Contributing
|
360
379
|
|
361
380
|
### Getting Help
|
362
381
|
Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/brownboxdev/rabarber/discussions) for:
|
@@ -1,35 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "dry-configurable"
|
4
4
|
|
5
5
|
module Rabarber
|
6
|
-
|
7
|
-
|
6
|
+
module Configuration
|
7
|
+
extend Dry::Configurable
|
8
8
|
|
9
|
-
|
10
|
-
attr_accessor :user_model_name
|
9
|
+
module_function
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
11
|
+
setting :cache_enabled,
|
12
|
+
default: true,
|
13
|
+
reader: true,
|
14
|
+
constructor: -> (value) do
|
15
|
+
Rabarber::Inputs::Boolean.new(
|
16
|
+
value,
|
17
|
+
error: Rabarber::ConfigurationError,
|
18
|
+
message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
|
19
|
+
).process
|
20
|
+
end
|
21
|
+
setting :current_user_method,
|
22
|
+
default: :current_user,
|
23
|
+
reader: true,
|
24
|
+
constructor: -> (value) do
|
25
|
+
Rabarber::Inputs::Symbol.new(
|
26
|
+
value,
|
27
|
+
error: Rabarber::ConfigurationError,
|
28
|
+
message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
|
29
|
+
).process
|
30
|
+
end
|
31
|
+
setting :user_model_name,
|
32
|
+
default: "User",
|
33
|
+
reader: true,
|
34
|
+
constructor: -> (value) do
|
35
|
+
Rabarber::Inputs::NonEmptyString.new(
|
36
|
+
value,
|
37
|
+
error: Rabarber::ConfigurationError,
|
38
|
+
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
|
39
|
+
).process
|
40
|
+
end
|
29
41
|
|
30
42
|
def user_model
|
31
|
-
Rabarber::
|
32
|
-
|
43
|
+
Rabarber::Inputs::Model.new(
|
44
|
+
user_model_name,
|
45
|
+
error: Rabarber::ConfigurationError,
|
46
|
+
message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{user_model_name.inspect}"
|
33
47
|
).process
|
34
48
|
end
|
35
49
|
end
|
@@ -19,13 +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::
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
Rabarber::Inputs::Symbol.new(
|
28
|
+
action,
|
29
|
+
optional: true,
|
30
|
+
message: "Expected a symbol or a string, got #{action.inspect}"
|
31
|
+
).process,
|
32
|
+
Rabarber::Inputs::Roles.new(
|
33
|
+
roles,
|
34
|
+
message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{roles.inspect}"
|
35
|
+
).process,
|
36
|
+
Rabarber::Inputs::Contexts::Authorizational.new(
|
37
|
+
context,
|
38
|
+
error: Rabarber::InvalidContextError,
|
39
|
+
message: "Expected a Class, an instance of ActiveRecord model, a symbol, a string, or a proc, got #{context.inspect}"
|
40
|
+
).resolve,
|
41
|
+
Rabarber::Inputs::DynamicRule.new(
|
42
|
+
if_rule,
|
43
|
+
optional: true,
|
44
|
+
message: "Expected a symbol, a string, or a proc, got #{if_rule.inspect}"
|
45
|
+
).process,
|
46
|
+
Rabarber::Inputs::DynamicRule.new(
|
47
|
+
unless_rule,
|
48
|
+
optional: true,
|
49
|
+
message: "Expected a symbol, a string, or a proc, got #{unless_rule.inspect}"
|
50
|
+
).process
|
29
51
|
)
|
30
52
|
end
|
31
53
|
end
|
data/lib/rabarber/core/cache.rb
CHANGED
@@ -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
|
@@ -4,12 +4,12 @@ module Rabarber
|
|
4
4
|
module Core
|
5
5
|
module Roleable
|
6
6
|
def roleable
|
7
|
-
current_roleable = send(Rabarber::Configuration.
|
7
|
+
current_roleable = send(Rabarber::Configuration.current_user_method)
|
8
8
|
|
9
|
-
unless current_roleable.is_a?(Rabarber::Configuration.
|
9
|
+
unless current_roleable.is_a?(Rabarber::Configuration.user_model)
|
10
10
|
raise(
|
11
11
|
Rabarber::Error,
|
12
|
-
"Expected `#{Rabarber::Configuration.
|
12
|
+
"Expected `#{Rabarber::Configuration.current_user_method}` to return an instance of #{Rabarber::Configuration.user_model_name}, got #{current_roleable.inspect}"
|
13
13
|
)
|
14
14
|
end
|
15
15
|
|
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,11 +33,17 @@ module Rabarber
|
|
35
33
|
end
|
36
34
|
|
37
35
|
def resolve_context(controller_instance)
|
38
|
-
case context
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
43
47
|
end
|
44
48
|
end
|
45
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 < 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 < 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 < 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
|
@@ -81,11 +81,18 @@ module Rabarber
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def process_role_names(role_names)
|
84
|
-
Rabarber::
|
84
|
+
Rabarber::Inputs::Roles.new(
|
85
|
+
role_names,
|
86
|
+
message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{role_names.inspect}"
|
87
|
+
).process
|
85
88
|
end
|
86
89
|
|
87
90
|
def process_context(context)
|
88
|
-
Rabarber::
|
91
|
+
Rabarber::Inputs::Context.new(
|
92
|
+
context,
|
93
|
+
error: Rabarber::InvalidContextError,
|
94
|
+
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
95
|
+
).resolve
|
89
96
|
end
|
90
97
|
|
91
98
|
def delete_roleable_cache(contexts:)
|
data/lib/rabarber/models/role.rb
CHANGED
@@ -6,7 +6,7 @@ module Rabarber
|
|
6
6
|
|
7
7
|
belongs_to :context, polymorphic: true, optional: true
|
8
8
|
|
9
|
-
has_and_belongs_to_many :roleables, class_name: Rabarber::Configuration.
|
9
|
+
has_and_belongs_to_many :roleables, class_name: Rabarber::Configuration.user_model_name,
|
10
10
|
association_foreign_key: "roleable_id",
|
11
11
|
join_table: "rabarber_roles_roleables"
|
12
12
|
|
@@ -63,24 +63,28 @@ module Rabarber
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def assignees(name, context: nil)
|
66
|
-
find_by(name: process_role_name(name), **process_context(context))&.roleables ||
|
67
|
-
Rabarber::Configuration.instance.user_model.none
|
66
|
+
find_by(name: process_role_name(name), **process_context(context))&.roleables || Rabarber::Configuration.user_model.none
|
68
67
|
end
|
69
68
|
|
70
69
|
private
|
71
70
|
|
72
71
|
def delete_roleables_cache(role, context:)
|
73
|
-
role.roleables.
|
74
|
-
Rabarber::Core::Cache.delete(*batch.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
|
75
|
-
end
|
72
|
+
Rabarber::Core::Cache.delete(*role.roleables.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
|
76
73
|
end
|
77
74
|
|
78
75
|
def process_role_name(name)
|
79
|
-
Rabarber::
|
76
|
+
Rabarber::Inputs::Role.new(
|
77
|
+
name,
|
78
|
+
message: "Expected a symbol or a string containing only lowercase letters, numbers, and underscores, got #{name.inspect}"
|
79
|
+
).process
|
80
80
|
end
|
81
81
|
|
82
82
|
def process_context(context)
|
83
|
-
Rabarber::
|
83
|
+
Rabarber::Inputs::Context.new(
|
84
|
+
context,
|
85
|
+
error: Rabarber::InvalidContextError,
|
86
|
+
message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
|
87
|
+
).resolve
|
84
88
|
end
|
85
89
|
end
|
86
90
|
|
data/lib/rabarber/railtie.rb
CHANGED
data/lib/rabarber/version.rb
CHANGED
data/lib/rabarber.rb
CHANGED
@@ -1,22 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "rabarber/version"
|
4
|
-
require_relative "rabarber/configuration"
|
5
4
|
|
6
5
|
require "active_record"
|
7
6
|
require "active_support"
|
8
7
|
|
9
|
-
require_relative "rabarber/
|
10
|
-
require_relative "rabarber/
|
11
|
-
require_relative "rabarber/
|
12
|
-
require_relative "rabarber/
|
13
|
-
require_relative "rabarber/
|
14
|
-
require_relative "rabarber/
|
15
|
-
require_relative "rabarber/
|
16
|
-
require_relative "rabarber/
|
17
|
-
require_relative "rabarber/
|
18
|
-
require_relative "rabarber/
|
19
|
-
|
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"
|
18
|
+
|
19
|
+
require_relative "rabarber/configuration"
|
20
|
+
|
21
|
+
module Rabarber
|
22
|
+
class Error < StandardError; end
|
23
|
+
class InvalidArgumentError < Rabarber::Error; end
|
24
|
+
class ConfigurationError < Rabarber::InvalidArgumentError; end
|
25
|
+
class InvalidContextError < Rabarber::InvalidArgumentError; end
|
26
|
+
class NotFoundError < Rabarber::Error; end
|
27
|
+
|
28
|
+
delegate :configure, to: Rabarber::Configuration
|
29
|
+
module_function :configure
|
30
|
+
end
|
20
31
|
|
21
32
|
require_relative "rabarber/core/cache"
|
22
33
|
|
@@ -32,15 +43,3 @@ require_relative "rabarber/core/permissions"
|
|
32
43
|
require_relative "rabarber/core/integrity_checker"
|
33
44
|
|
34
45
|
require_relative "rabarber/railtie"
|
35
|
-
|
36
|
-
module Rabarber
|
37
|
-
class Error < StandardError; end
|
38
|
-
class ConfigurationError < Rabarber::Error; end
|
39
|
-
class InvalidArgumentError < Rabarber::Error; end
|
40
|
-
class NotFoundError < Rabarber::Error; end
|
41
|
-
|
42
|
-
def configure
|
43
|
-
yield(Rabarber::Configuration.instance)
|
44
|
-
end
|
45
|
-
module_function :configure
|
46
|
-
end
|
data/rabarber.gemspec
CHANGED
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.1.
|
4
|
+
version: 5.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- enjaku4
|
@@ -10,6 +10,34 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 1980-01-02 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'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: rails
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,17 +78,16 @@ files:
|
|
50
78
|
- lib/rabarber/core/rule.rb
|
51
79
|
- lib/rabarber/helpers/helpers.rb
|
52
80
|
- lib/rabarber/helpers/migration_helpers.rb
|
53
|
-
- lib/rabarber/
|
54
|
-
- lib/rabarber/
|
55
|
-
- lib/rabarber/
|
56
|
-
- lib/rabarber/
|
57
|
-
- lib/rabarber/
|
58
|
-
- lib/rabarber/
|
59
|
-
- lib/rabarber/
|
60
|
-
- lib/rabarber/
|
61
|
-
- lib/rabarber/
|
62
|
-
- lib/rabarber/
|
63
|
-
- lib/rabarber/input/types/symbol.rb
|
81
|
+
- lib/rabarber/inputs/base.rb
|
82
|
+
- lib/rabarber/inputs/boolean.rb
|
83
|
+
- lib/rabarber/inputs/context.rb
|
84
|
+
- lib/rabarber/inputs/contexts/authorizational.rb
|
85
|
+
- lib/rabarber/inputs/dynamic_rule.rb
|
86
|
+
- lib/rabarber/inputs/model.rb
|
87
|
+
- lib/rabarber/inputs/non_empty_string.rb
|
88
|
+
- lib/rabarber/inputs/role.rb
|
89
|
+
- lib/rabarber/inputs/roles.rb
|
90
|
+
- lib/rabarber/inputs/symbol.rb
|
64
91
|
- lib/rabarber/models/concerns/has_roles.rb
|
65
92
|
- lib/rabarber/models/role.rb
|
66
93
|
- lib/rabarber/railtie.rb
|
@@ -1,21 +0,0 @@
|
|
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
|
-
value&.to_sym
|
14
|
-
end
|
15
|
-
|
16
|
-
def default_error_message
|
17
|
-
"Action name must be a Symbol or a String"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class ArModel < Rabarber::Input::Base
|
6
|
-
def valid?
|
7
|
-
processed_value < ActiveRecord::Base
|
8
|
-
rescue NameError
|
9
|
-
false
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def processed_value
|
15
|
-
value.constantize
|
16
|
-
end
|
17
|
-
|
18
|
-
def default_error_message
|
19
|
-
"Value must be an ActiveRecord model"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class AuthorizationContext < Rabarber::Input::Base
|
6
|
-
def valid?
|
7
|
-
Rabarber::Input::Context.new(value).valid? || Rabarber::Input::DynamicRule.new(value).valid?
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def processed_value
|
13
|
-
case value
|
14
|
-
when String then value.to_sym
|
15
|
-
when Symbol, Proc then value
|
16
|
-
else Rabarber::Input::Context.new(value).process
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def default_error_message
|
21
|
-
"Context must be a Class, an instance of ActiveRecord model, a Symbol, a String, or a Proc"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/lib/rabarber/input/base.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class Base
|
6
|
-
attr_reader :value, :error_type, :error_message
|
7
|
-
|
8
|
-
def initialize(value, error_type = Rabarber::InvalidArgumentError, error_message = nil)
|
9
|
-
@value = value
|
10
|
-
@error_type = error_type
|
11
|
-
@error_message = error_message || default_error_message
|
12
|
-
end
|
13
|
-
|
14
|
-
def process
|
15
|
-
valid? ? processed_value : raise_error
|
16
|
-
end
|
17
|
-
|
18
|
-
def valid?
|
19
|
-
raise NotImplementedError
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def processed_value
|
25
|
-
raise NotImplementedError
|
26
|
-
end
|
27
|
-
|
28
|
-
def default_error_message
|
29
|
-
raise NotImplementedError
|
30
|
-
end
|
31
|
-
|
32
|
-
def raise_error
|
33
|
-
raise error_type, error_message
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class Context < Rabarber::Input::Base
|
6
|
-
def valid?
|
7
|
-
value.nil? || value.is_a?(Class) || value.is_a?(ActiveRecord::Base) && value.persisted? || already_processed?
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def processed_value
|
13
|
-
case value
|
14
|
-
when nil then { context_type: nil, context_id: nil }
|
15
|
-
when Class then { context_type: value.to_s, context_id: nil }
|
16
|
-
when ActiveRecord::Base then { context_type: value.class.to_s, context_id: value.public_send(value.class.primary_key) }
|
17
|
-
else value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def default_error_message
|
22
|
-
"Context must be a Class or an instance of ActiveRecord model"
|
23
|
-
end
|
24
|
-
|
25
|
-
def already_processed?
|
26
|
-
case value
|
27
|
-
in { context_type: NilClass | String, context_id: NilClass | String | Integer } then true
|
28
|
-
else false
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,21 +0,0 @@
|
|
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
|
-
value.is_a?(String) ? value.to_sym : value
|
14
|
-
end
|
15
|
-
|
16
|
-
def default_error_message
|
17
|
-
"Dynamic rule must be a Symbol, a String, or a Proc"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/rabarber/input/role.rb
DELETED
@@ -1,23 +0,0 @@
|
|
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
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
class Roles < Rabarber::Input::Base
|
6
|
-
def value
|
7
|
-
Array(super)
|
8
|
-
end
|
9
|
-
|
10
|
-
def valid?
|
11
|
-
value.all? { |role_name| Rabarber::Input::Role.new(role_name).valid? }
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def processed_value
|
17
|
-
value.map(&:to_sym)
|
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
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
module Types
|
6
|
-
class Boolean < Rabarber::Input::Base
|
7
|
-
def valid?
|
8
|
-
[true, false].include?(value)
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def processed_value
|
14
|
-
value
|
15
|
-
end
|
16
|
-
|
17
|
-
def default_error_message
|
18
|
-
"Value must be a Boolean"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
module Types
|
6
|
-
class Proc < Rabarber::Input::Base
|
7
|
-
def valid?
|
8
|
-
value.is_a?(::Proc)
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def processed_value
|
14
|
-
value
|
15
|
-
end
|
16
|
-
|
17
|
-
def default_error_message
|
18
|
-
"Value must be a Proc"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module Input
|
5
|
-
module Types
|
6
|
-
class Symbol < Rabarber::Input::Base
|
7
|
-
def valid?
|
8
|
-
(value.is_a?(::Symbol) || value.is_a?(String)) && value.present?
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def processed_value
|
14
|
-
value.to_sym
|
15
|
-
end
|
16
|
-
|
17
|
-
def default_error_message
|
18
|
-
"Value must be a Symbol or a String"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|