rabarber 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +20 -6
- data/lib/rabarber/access.rb +9 -9
- data/lib/rabarber/controllers/concerns/authorization.rb +4 -2
- data/lib/rabarber/permissions.rb +2 -2
- data/lib/rabarber/rule.rb +24 -17
- data/lib/rabarber/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf7a31c94f695cb0edb8209562d07971f3fc712bd868bf589fd4b1ee4ffb2865
|
4
|
+
data.tar.gz: 2c5d939673b685beba64910dc4ee361f811d760cc8bb6982b502b3e0f4354c5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c513a7ea4140a12b37384b0bc91969eec5305f1d422a24d3a6117607d9b1b0dbdaa77398f4ce49fa2025b9fb1101439c09c274cded09acbb58a46b0bd54b9b3b
|
7
|
+
data.tar.gz: 93f18fe608254896b97e772e05d9c94e31f17f769fd4d4f5314460450d19408c7f3f41a1197a7834b43ab81e6bbf694446ed94b23df9af6b9e904040534afc1a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 1.1.0
|
2
|
+
|
3
|
+
- Add support for `unless` argument in `grant_access` method, allowing to define negated dynamic rules
|
4
|
+
- Fix a bug where specifying a dynamic rule as a symbol without specifying an action would result in an error
|
5
|
+
|
1
6
|
## 1.0.5
|
2
7
|
|
3
8
|
- Add co-author: [trafium](https://github.com/trafium)
|
data/README.md
CHANGED
@@ -3,13 +3,13 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/rabarber.svg)](http://badge.fury.io/rb/rabarber)
|
4
4
|
[![Github Actions badge](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
|
5
5
|
|
6
|
-
Rabarber is an authorization library for Ruby on Rails, primarily designed for use in the
|
6
|
+
Rabarber is an authorization library for Ruby on Rails, primarily designed for use in the application layer but not limited to that. It offers a set of useful tools for managing user roles and defining authorization rules.
|
7
7
|
|
8
8
|
---
|
9
9
|
|
10
10
|
**Example of Usage**:
|
11
11
|
|
12
|
-
Consider a CRM where users with different roles have distinct access levels. For instance, the role
|
12
|
+
Consider a CRM where users with different roles have distinct access levels. For instance, the role `accountant` can interact with invoices but cannot access marketing information, while the role `marketer` has access to marketing-related data. Such authorization rules can be easily defined with Rabarber.
|
13
13
|
|
14
14
|
---
|
15
15
|
|
@@ -145,7 +145,11 @@ Rabarber::Role.names
|
|
145
145
|
|
146
146
|
`Rabarber::Role` is a model that represents roles within your application. It has a single attribute, `name`, which is validated for both uniqueness and presence. You can treat `Rabarber::Role` as a regular Rails model and use Active Record methods on it if necessary.
|
147
147
|
|
148
|
-
Utilize the aforementioned methods to manipulate user roles. For example, create a
|
148
|
+
*Utilize the aforementioned methods to manipulate user roles. For example, create a UI for managing roles or assign roles during migration or runtime (e.g. when the user is created).*
|
149
|
+
|
150
|
+
*You are also encouraged to write your own authorization policies based on `#has_role?` method (e.g. to scope the data that the role can access).*
|
151
|
+
|
152
|
+
*Adapt the tools Rabarber provides to fit the requirements of your application.*
|
149
153
|
|
150
154
|
## Authorization Rules
|
151
155
|
|
@@ -221,11 +225,12 @@ This allows everyone to access `OrdersController` and its children and `index` a
|
|
221
225
|
|
222
226
|
If you've set `must_have_roles` setting to `true`, then, only the users with at least one role can have access. This setting can be useful if your requirements are such that users without roles are not allowed to see anything.
|
223
227
|
|
224
|
-
For more complex
|
228
|
+
For more complex cases, Rabarber provides dynamic rules:
|
225
229
|
|
226
230
|
```rb
|
227
231
|
class OrdersController < ApplicationController
|
228
232
|
grant_access if: :user_has_access?
|
233
|
+
grant_access unless: :user_has_no_access?
|
229
234
|
...
|
230
235
|
|
231
236
|
private
|
@@ -233,6 +238,10 @@ class OrdersController < ApplicationController
|
|
233
238
|
def user_has_access?
|
234
239
|
...
|
235
240
|
end
|
241
|
+
|
242
|
+
def user_has_no_access?
|
243
|
+
...
|
244
|
+
end
|
236
245
|
end
|
237
246
|
|
238
247
|
class InvoicesController < ApplicationController
|
@@ -240,11 +249,16 @@ class InvoicesController < ApplicationController
|
|
240
249
|
def index
|
241
250
|
...
|
242
251
|
end
|
252
|
+
|
253
|
+
grant_access action: :show, roles: :client, unless: -> { current_user.banned? }
|
254
|
+
def show
|
255
|
+
...
|
256
|
+
end
|
243
257
|
end
|
244
258
|
```
|
245
|
-
You can pass a
|
259
|
+
You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol (the method with the same name will be called) or a lambda.
|
246
260
|
|
247
|
-
Rules defined in
|
261
|
+
Rules defined in child classes don't override parent rules but rather add to them:
|
248
262
|
```rb
|
249
263
|
class Crm::BaseController < ApplicationController
|
250
264
|
grant_access roles: :admin
|
data/lib/rabarber/access.rb
CHANGED
@@ -2,25 +2,25 @@
|
|
2
2
|
|
3
3
|
module Rabarber
|
4
4
|
module Access
|
5
|
-
def access_granted?(roles, controller, action,
|
6
|
-
controller_accessible?(roles, controller,
|
7
|
-
action_accessible?(roles, controller, action,
|
5
|
+
def access_granted?(roles, controller, action, dynamic_rule_receiver)
|
6
|
+
controller_accessible?(roles, controller, dynamic_rule_receiver) ||
|
7
|
+
action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
8
8
|
end
|
9
9
|
|
10
|
-
def controller_accessible?(roles, controller,
|
11
|
-
accessible_controllers(roles,
|
10
|
+
def controller_accessible?(roles, controller, dynamic_rule_receiver)
|
11
|
+
accessible_controllers(roles, dynamic_rule_receiver).any? do |accessible_controller|
|
12
12
|
controller <= accessible_controller
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def action_accessible?(roles, controller, action,
|
17
|
-
action_rules[controller].any? { |rule| rule.verify_access(roles,
|
16
|
+
def action_accessible?(roles, controller, action, dynamic_rule_receiver)
|
17
|
+
action_rules[controller].any? { |rule| rule.verify_access(roles, dynamic_rule_receiver, action) }
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def accessible_controllers(roles,
|
23
|
-
controller_rules.select { |_, rule| rule.verify_access(roles,
|
22
|
+
def accessible_controllers(roles, dynamic_rule_receiver)
|
23
|
+
controller_rules.select { |_, rule| rule.verify_access(roles, dynamic_rule_receiver) }.keys
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -9,8 +9,10 @@ module Rabarber
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class_methods do
|
12
|
-
def grant_access(action: nil, roles: nil, if: nil)
|
13
|
-
|
12
|
+
def grant_access(action: nil, roles: nil, if: nil, unless: nil)
|
13
|
+
dynamic_rule, negated_dynamic_rule = binding.local_variable_get(:if), binding.local_variable_get(:unless)
|
14
|
+
|
15
|
+
Permissions.write(self, action, roles, dynamic_rule, negated_dynamic_rule)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
data/lib/rabarber/permissions.rb
CHANGED
@@ -15,8 +15,8 @@ module Rabarber
|
|
15
15
|
@storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.write(controller, action, roles,
|
19
|
-
rule = Rule.new(action, roles,
|
18
|
+
def self.write(controller, action, roles, dynamic_rule, negated_dynamic_rule)
|
19
|
+
rule = Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
20
20
|
|
21
21
|
if action
|
22
22
|
instance.storage[:action_rules][controller] += [rule]
|
data/lib/rabarber/rule.rb
CHANGED
@@ -2,16 +2,17 @@
|
|
2
2
|
|
3
3
|
module Rabarber
|
4
4
|
class Rule
|
5
|
-
attr_reader :action, :roles, :
|
5
|
+
attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
|
6
6
|
|
7
|
-
def initialize(action, roles,
|
7
|
+
def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
|
8
8
|
@action = pre_process_action(action)
|
9
9
|
@roles = RoleNames.pre_process(Array(roles))
|
10
|
-
@
|
10
|
+
@dynamic_rule = pre_process_dynamic_rule(dynamic_rule)
|
11
|
+
@negated_dynamic_rule = pre_process_dynamic_rule(negated_dynamic_rule)
|
11
12
|
end
|
12
13
|
|
13
|
-
def verify_access(user_roles,
|
14
|
-
action_accessible?(action_name) && roles_permitted?(user_roles) &&
|
14
|
+
def verify_access(user_roles, dynamic_rule_receiver, action_name = nil)
|
15
|
+
action_accessible?(action_name) && roles_permitted?(user_roles) && dynamic_rule_followed?(dynamic_rule_receiver)
|
15
16
|
end
|
16
17
|
|
17
18
|
def action_accessible?(action_name)
|
@@ -24,18 +25,24 @@ module Rabarber
|
|
24
25
|
roles.empty? || (roles & user_roles).any?
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
|
28
|
+
def dynamic_rule_followed?(dynamic_rule_receiver)
|
29
|
+
!!(execute_dynamic_rule(dynamic_rule_receiver, false) && execute_dynamic_rule(dynamic_rule_receiver, true))
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
32
33
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
def execute_dynamic_rule(dynamic_rule_receiver, is_negated)
|
35
|
+
rule = is_negated ? negated_dynamic_rule : dynamic_rule
|
36
|
+
|
37
|
+
return true if rule.nil?
|
38
|
+
|
39
|
+
result = if rule.is_a?(Proc)
|
40
|
+
dynamic_rule_receiver.instance_exec(&rule)
|
41
|
+
else
|
42
|
+
dynamic_rule_receiver.send(rule)
|
43
|
+
end
|
44
|
+
|
45
|
+
is_negated ? !result : result
|
39
46
|
end
|
40
47
|
|
41
48
|
def pre_process_action(action)
|
@@ -45,11 +52,11 @@ module Rabarber
|
|
45
52
|
raise InvalidArgumentError, "Action name must be a Symbol or a String"
|
46
53
|
end
|
47
54
|
|
48
|
-
def
|
49
|
-
return
|
50
|
-
return
|
55
|
+
def pre_process_dynamic_rule(rule)
|
56
|
+
return rule.to_sym if (rule.is_a?(String) || rule.is_a?(Symbol)) && rule.present?
|
57
|
+
return rule if rule.nil? || rule.is_a?(Proc)
|
51
58
|
|
52
|
-
raise InvalidArgumentError, "
|
59
|
+
raise InvalidArgumentError, "Dynamic rule must be a Symbol, a String, or a Proc"
|
53
60
|
end
|
54
61
|
end
|
55
62
|
end
|
data/lib/rabarber/version.rb
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: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- enjaku4
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-01-
|
12
|
+
date: 2024-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|