rabarber 1.1.0 → 1.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 +5 -0
- data/README.md +14 -14
- data/lib/generators/rabarber/templates/create_rabarber_roles.rb.erb +1 -1
- data/lib/rabarber/configuration.rb +59 -19
- data/lib/rabarber/controllers/concerns/authorization.rb +13 -4
- data/lib/rabarber/helpers/helpers.rb +2 -2
- data/lib/rabarber/input/actions.rb +30 -0
- data/lib/rabarber/input/base.rb +33 -0
- data/lib/rabarber/input/dynamic_rules.rb +30 -0
- data/lib/rabarber/input/roles.rb +32 -0
- data/lib/rabarber/input/types/booleans.rb +19 -0
- data/lib/rabarber/input/types/procs.rb +19 -0
- data/lib/rabarber/input/types/symbols.rb +19 -0
- data/lib/rabarber/missing/actions.rb +24 -0
- data/lib/rabarber/missing/base.rb +62 -0
- data/lib/rabarber/missing/roles.rb +35 -0
- data/lib/rabarber/models/concerns/has_roles.rb +13 -7
- data/lib/rabarber/models/role.rb +1 -1
- data/lib/rabarber/permissions.rb +2 -2
- data/lib/rabarber/railtie.rb +14 -0
- data/lib/rabarber/rule.rb +5 -19
- data/lib/rabarber/version.rb +1 -1
- data/lib/rabarber.rb +18 -6
- metadata +13 -3
- data/lib/rabarber/role_names.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7eaa4eaef0f5c6d072e3a5b545b0ce152651df15b521be933aac78e1add85ba
|
4
|
+
data.tar.gz: d0557fcd900f2bebd58f6489c2f3f79c2f95c7c14bbeeeb59a294fdfcbcb3f49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cadc08751bf39ab0c6eb8a357854fe00bb478533a7f5a7e0760929d6acedad0fcecb31e17c21c980c764d59302a569df74e847f09c778b2e3f1c257c520c4336
|
7
|
+
data.tar.gz: 7067e81820ce07c0929e3f18ad079c9cfa44b0dc473138b042e4e882b8db1cda17bbc680a431634c42695434a088605fd897ab4a2e25a4d4d2b9bac7c8180fd5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 1.2.0
|
2
|
+
|
3
|
+
- Enhance handling of missing actions and roles specified in `grant_access` method by raising an error for missing actions and logging a warning for missing roles.
|
4
|
+
- Introduce `when_actions_missing` and `when_roles_missing` configuration options, allowing to customize the behavior when actions or roles are not found.
|
5
|
+
|
1
6
|
## 1.1.0
|
2
7
|
|
3
8
|
- Add support for `unless` argument in `grant_access` method, allowing to define negated dynamic rules
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[](http://badge.fury.io/rb/rabarber)
|
4
4
|
[](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
|
5
5
|
|
6
|
-
Rabarber is
|
6
|
+
Rabarber is a role-based 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
|
|
@@ -63,20 +63,26 @@ rails db:migrate
|
|
63
63
|
|
64
64
|
## Configuration
|
65
65
|
|
66
|
-
Rabarber can be configured by
|
66
|
+
Rabarber can be configured by using `.configure` method in an initializer:
|
67
67
|
|
68
68
|
```rb
|
69
69
|
Rabarber.configure do |config|
|
70
70
|
config.current_user_method = :authenticated_user
|
71
71
|
config.must_have_roles = true
|
72
|
-
config.
|
73
|
-
|
74
|
-
}
|
72
|
+
config.when_actions_missing = -> (missing_actions, context) { ... }
|
73
|
+
config.when_roles_missing = -> (missing_roles, context) { ... }
|
74
|
+
config.when_unauthorized = -> (controller) { ... }
|
75
75
|
end
|
76
76
|
```
|
77
77
|
- `current_user_method` must be a symbol representing the method that returns the currently authenticated user. The default value is `:current_user`.
|
78
|
+
|
78
79
|
- `must_have_roles` must be a boolean determining whether a user with no roles can access endpoints permitted to everyone. The default value is `false` (allowing users without roles to access endpoints permitted to everyone).
|
79
|
-
|
80
|
+
|
81
|
+
- `when_actions_missing` must be a proc where you can define the behaviour when the actions specified in `grant_access` method cannot be found in the controller (`missing_actions` is an array of missing actions, `context` is a hash that looks like this: `{ controller: "InvoicesController" }`). This check is performed on every request and when the application is initialized if `eager_load` configuration is enabled in Rails. By default, an error is raised when actions are missing.
|
82
|
+
|
83
|
+
- `when_roles_missing` must be a proc where you can define the behaviour when the roles specified in `grant_access` method cannot be found in the database (`missing_roles` is an array of missing roles, `context` is a hash that looks like this: `{ controller: "InvoicesController", action: "index" }`). This check is performed on every request and when the application is initialized if `eager_load` configuration is enabled in Rails. By default, only a warning is logged when roles are missing.
|
84
|
+
|
85
|
+
- `when_unauthorized` must be a proc where you can define the behaviour when access is not authorized (`controller` is an instance of the controller where the code is executed). By default, the user is redirected back if the request format is HTML; otherwise, a 401 Unauthorized response is sent.
|
80
86
|
|
81
87
|
## Roles
|
82
88
|
|
@@ -108,7 +114,7 @@ You can also explicitly create new roles simply by using:
|
|
108
114
|
```rb
|
109
115
|
Rabarber::Role.create(name: "manager")
|
110
116
|
```
|
111
|
-
The role names
|
117
|
+
The role names must be unique.
|
112
118
|
|
113
119
|
**`#revoke_roles`**
|
114
120
|
|
@@ -145,12 +151,6 @@ Rabarber::Role.names
|
|
145
151
|
|
146
152
|
`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
153
|
|
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.*
|
153
|
-
|
154
154
|
## Authorization Rules
|
155
155
|
|
156
156
|
Include `Rabarber::Authorization` module into the controller that needs authorization rules to be applied (authorization rules will be applied to the controller and its children). Typically, it is `ApplicationController`, but it can be any controller.
|
@@ -256,7 +256,7 @@ class InvoicesController < ApplicationController
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
```
|
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
|
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 proc.
|
260
260
|
|
261
261
|
Rules defined in child classes don't override parent rules but rather add to them:
|
262
262
|
```rb
|
@@ -12,6 +12,6 @@ class CreateRabarberRoles < ActiveRecord::Migration[<%= ActiveRecord::Migration.
|
|
12
12
|
t.belongs_to :roleable, null: false, index: true, foreign_key: { to_table: raise(Rabarber::Error, "Please specify your user model's table name") }
|
13
13
|
end
|
14
14
|
|
15
|
-
add_index :rabarber_roles_roleables,
|
15
|
+
add_index :rabarber_roles_roleables, [:role_id, :roleable_id], unique: true
|
16
16
|
end
|
17
17
|
end
|
@@ -6,38 +6,78 @@ module Rabarber
|
|
6
6
|
class Configuration
|
7
7
|
include Singleton
|
8
8
|
|
9
|
-
attr_reader :current_user_method, :must_have_roles, :when_unauthorized
|
9
|
+
attr_reader :current_user_method, :must_have_roles, :when_actions_missing, :when_roles_missing, :when_unauthorized
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
@current_user_method =
|
13
|
-
@must_have_roles =
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
controller.head(:unauthorized)
|
19
|
-
end
|
20
|
-
end
|
12
|
+
@current_user_method = default_current_user_method
|
13
|
+
@must_have_roles = default_must_have_roles
|
14
|
+
@when_actions_missing = default_when_actions_missing
|
15
|
+
@when_roles_missing = default_when_roles_missing
|
16
|
+
@when_unauthorized = default_when_unauthorized
|
21
17
|
end
|
22
18
|
|
23
19
|
def current_user_method=(method_name)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@current_user_method = method_name.to_sym
|
20
|
+
@current_user_method = Rabarber::Input::Types::Symbols.new(
|
21
|
+
method_name, Rabarber::ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
|
22
|
+
).process
|
29
23
|
end
|
30
24
|
|
31
25
|
def must_have_roles=(value)
|
32
|
-
|
26
|
+
@must_have_roles = Rabarber::Input::Types::Booleans.new(
|
27
|
+
value, Rabarber::ConfigurationError, "Configuration 'must_have_roles' must be a Boolean"
|
28
|
+
).process
|
29
|
+
end
|
30
|
+
|
31
|
+
def when_actions_missing=(callable)
|
32
|
+
@when_actions_missing = Rabarber::Input::Types::Procs.new(
|
33
|
+
callable, Rabarber::ConfigurationError, "Configuration 'when_actions_missing' must be a Proc"
|
34
|
+
).process
|
35
|
+
end
|
33
36
|
|
34
|
-
|
37
|
+
def when_roles_missing=(callable)
|
38
|
+
@when_roles_missing = Rabarber::Input::Types::Procs.new(
|
39
|
+
callable, Rabarber::ConfigurationError, "Configuration 'when_roles_missing' must be a Proc"
|
40
|
+
).process
|
35
41
|
end
|
36
42
|
|
37
43
|
def when_unauthorized=(callable)
|
38
|
-
|
44
|
+
@when_unauthorized = Rabarber::Input::Types::Procs.new(
|
45
|
+
callable, Rabarber::ConfigurationError, "Configuration 'when_unauthorized' must be a Proc"
|
46
|
+
).process
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
39
50
|
|
40
|
-
|
51
|
+
def default_current_user_method
|
52
|
+
:current_user
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_must_have_roles
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_when_actions_missing
|
60
|
+
-> (missing_actions, context) {
|
61
|
+
raise Rabarber::Error, "Missing actions: #{missing_actions}, context: #{context[:controller]}"
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def default_when_roles_missing
|
66
|
+
-> (missing_roles, context) {
|
67
|
+
delimiter = context[:action] ? "#" : ""
|
68
|
+
message = "Missing roles: #{missing_roles}, context: #{context[:controller]}#{delimiter}#{context[:action]}"
|
69
|
+
Rails.logger.tagged("Rabarber") { Rails.logger.warn message }
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_when_unauthorized
|
74
|
+
-> (controller) do
|
75
|
+
if controller.request.format.html?
|
76
|
+
controller.redirect_back fallback_location: controller.main_app.root_path
|
77
|
+
else
|
78
|
+
controller.head(:unauthorized)
|
79
|
+
end
|
80
|
+
end
|
41
81
|
end
|
42
82
|
end
|
43
83
|
end
|
@@ -12,18 +12,27 @@ module Rabarber
|
|
12
12
|
def grant_access(action: nil, roles: nil, if: nil, unless: nil)
|
13
13
|
dynamic_rule, negated_dynamic_rule = binding.local_variable_get(:if), binding.local_variable_get(:unless)
|
14
14
|
|
15
|
-
Permissions.
|
15
|
+
Rabarber::Permissions.add(
|
16
|
+
self,
|
17
|
+
Rabarber::Input::Actions.new(action).process,
|
18
|
+
Rabarber::Input::Roles.new(roles).process,
|
19
|
+
Rabarber::Input::DynamicRules.new(dynamic_rule).process,
|
20
|
+
Rabarber::Input::DynamicRules.new(negated_dynamic_rule).process
|
21
|
+
)
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
19
25
|
private
|
20
26
|
|
21
27
|
def verify_access
|
22
|
-
|
23
|
-
|
28
|
+
Rabarber::Missing::Actions.new(self.class).handle
|
29
|
+
Rabarber::Missing::Roles.new(self.class).handle
|
30
|
+
|
31
|
+
return if Rabarber::Permissions.access_granted?(
|
32
|
+
send(Rabarber::Configuration.instance.current_user_method).roles, self.class, action_name.to_sym, self
|
24
33
|
)
|
25
34
|
|
26
|
-
|
35
|
+
Rabarber::Configuration.instance.when_unauthorized.call(self)
|
27
36
|
end
|
28
37
|
end
|
29
38
|
end
|
@@ -3,13 +3,13 @@
|
|
3
3
|
module Rabarber
|
4
4
|
module Helpers
|
5
5
|
def visible_to(*roles, &block)
|
6
|
-
return unless send(
|
6
|
+
return unless send(Rabarber::Configuration.instance.current_user_method).has_role?(*roles)
|
7
7
|
|
8
8
|
capture(&block)
|
9
9
|
end
|
10
10
|
|
11
11
|
def hidden_from(*roles, &block)
|
12
|
-
return if send(
|
12
|
+
return if send(Rabarber::Configuration.instance.current_user_method).has_role?(*roles)
|
13
13
|
|
14
14
|
capture(&block)
|
15
15
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class Actions < Rabarber::Input::Base
|
6
|
+
def initialize(
|
7
|
+
value,
|
8
|
+
error_type = Rabarber::InvalidArgumentError,
|
9
|
+
error_message = "Action name must be a Symbol or a String"
|
10
|
+
)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
(value.is_a?(String) || value.is_a?(Symbol)) && value.present? || value.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def processed_value
|
21
|
+
case value
|
22
|
+
when String, Symbol
|
23
|
+
value.to_sym
|
24
|
+
when nil
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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, error_message)
|
9
|
+
@value = value
|
10
|
+
@error_type = error_type
|
11
|
+
@error_message = error_message
|
12
|
+
end
|
13
|
+
|
14
|
+
def process
|
15
|
+
valid? ? processed_value : raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
def processed_value
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_error
|
29
|
+
raise error_type, error_message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class DynamicRules < Rabarber::Input::Base
|
6
|
+
def initialize(
|
7
|
+
value,
|
8
|
+
error_type = Rabarber::InvalidArgumentError,
|
9
|
+
error_message = "Dynamic rule must be a Symbol, a String, or a Proc"
|
10
|
+
)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
(value.is_a?(String) || value.is_a?(Symbol)) && value.present? || value.nil? || value.is_a?(Proc)
|
18
|
+
end
|
19
|
+
|
20
|
+
def processed_value
|
21
|
+
case value
|
22
|
+
when String, Symbol
|
23
|
+
value.to_sym
|
24
|
+
when Proc, nil
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
class Roles < Rabarber::Input::Base
|
6
|
+
REGEX = /\A[a-z0-9_]+\z/
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
value,
|
10
|
+
error_type = Rabarber::InvalidArgumentError,
|
11
|
+
error_message =
|
12
|
+
"Role names must be Symbols or Strings and may only contain lowercase letters, numbers and underscores"
|
13
|
+
)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
Array(super)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
value.all? { |role_name| (role_name.is_a?(Symbol) || role_name.is_a?(String)) && role_name.to_s.match?(REGEX) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def processed_value
|
28
|
+
value.map(&:to_sym)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
module Types
|
6
|
+
class Booleans < Rabarber::Input::Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
[true, false].include?(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def processed_value
|
14
|
+
value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Input
|
5
|
+
module Types
|
6
|
+
class Symbols < Rabarber::Input::Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
(value.is_a?(Symbol) || value.is_a?(String)) && value.present?
|
11
|
+
end
|
12
|
+
|
13
|
+
def processed_value
|
14
|
+
value.to_sym
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Missing
|
5
|
+
class Actions < Rabarber::Missing::Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def check_controller_rules
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_action_rules
|
13
|
+
action_rules.each do |controller, controller_action_rules|
|
14
|
+
missing_actions = controller_action_rules.map(&:action) - controller.action_methods.map(&:to_sym)
|
15
|
+
missing_list << Rabarber::Missing::Item.new(missing_actions, controller, nil) if missing_actions.present?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def configuration_name
|
20
|
+
:when_actions_missing
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Missing
|
5
|
+
class Base
|
6
|
+
attr_reader :controller
|
7
|
+
|
8
|
+
def initialize(controller = nil)
|
9
|
+
@controller = controller
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle
|
13
|
+
check_controller_rules
|
14
|
+
check_action_rules
|
15
|
+
|
16
|
+
return if missing_list.empty?
|
17
|
+
|
18
|
+
missing_list.each do |item|
|
19
|
+
Rabarber::Configuration.instance.public_send(configuration_name).call(
|
20
|
+
item.missing, controller: item.controller, action: item.action
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def check_controller_rules
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_action_rules
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def configuration_name
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def missing_list
|
40
|
+
@missing_list ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def controller_rules
|
44
|
+
if controller
|
45
|
+
{ controller => Rabarber::Permissions.controller_rules[controller] }
|
46
|
+
else
|
47
|
+
Rabarber::Permissions.controller_rules
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def action_rules
|
52
|
+
if controller
|
53
|
+
{ controller => Rabarber::Permissions.action_rules[controller] }
|
54
|
+
else
|
55
|
+
Rabarber::Permissions.action_rules
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Item = Struct.new(:missing, :controller, :action)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rabarber
|
4
|
+
module Missing
|
5
|
+
class Roles < Rabarber::Missing::Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def check_controller_rules
|
9
|
+
controller_rules.each do |controller, controller_rule|
|
10
|
+
missing_roles = controller_rule.roles - all_roles if controller_rule.present?
|
11
|
+
missing_list << Rabarber::Missing::Item.new(missing_roles, controller, nil) if missing_roles.present?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_action_rules
|
16
|
+
action_rules.each do |controller, controller_action_rules|
|
17
|
+
controller_action_rules.each do |action_rule|
|
18
|
+
missing_roles = action_rule.roles - all_roles
|
19
|
+
if missing_roles.any?
|
20
|
+
missing_list << Rabarber::Missing::Item.new(missing_roles, controller, action_rule.action)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def configuration_name
|
27
|
+
:when_roles_missing
|
28
|
+
end
|
29
|
+
|
30
|
+
def all_roles
|
31
|
+
@all_roles ||= Rabarber::Role.names
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -5,7 +5,9 @@ module Rabarber
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
|
8
|
+
if defined?(@@included) && @@included != name
|
9
|
+
raise Rabarber::Error, "Rabarber::HasRoles can only be included once"
|
10
|
+
end
|
9
11
|
|
10
12
|
@@included = name
|
11
13
|
|
@@ -19,26 +21,30 @@ module Rabarber
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def has_role?(*role_names)
|
22
|
-
(roles &
|
24
|
+
(roles & process_role_names(role_names)).any?
|
23
25
|
end
|
24
26
|
|
25
27
|
def assign_roles(*role_names, create_new: true)
|
26
|
-
roles_to_assign =
|
28
|
+
roles_to_assign = process_role_names(role_names)
|
27
29
|
|
28
30
|
create_new_roles(roles_to_assign) if create_new
|
29
31
|
|
30
|
-
rabarber_roles << Role.where(name: roles_to_assign) - rabarber_roles
|
32
|
+
rabarber_roles << Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
|
31
33
|
end
|
32
34
|
|
33
35
|
def revoke_roles(*role_names)
|
34
|
-
self.rabarber_roles = rabarber_roles - Role.where(name:
|
36
|
+
self.rabarber_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
|
35
37
|
end
|
36
38
|
|
37
39
|
private
|
38
40
|
|
39
41
|
def create_new_roles(role_names)
|
40
|
-
new_roles = role_names - Role.names
|
41
|
-
new_roles.each { |role_name| Role.create!(name: role_name) }
|
42
|
+
new_roles = role_names - Rabarber::Role.names
|
43
|
+
new_roles.each { |role_name| Rabarber::Role.create!(name: role_name) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_role_names(role_names)
|
47
|
+
Rabarber::Input::Roles.new(role_names).process
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
data/lib/rabarber/models/role.rb
CHANGED
@@ -4,7 +4,7 @@ module Rabarber
|
|
4
4
|
class Role < ActiveRecord::Base
|
5
5
|
self.table_name = "rabarber_roles"
|
6
6
|
|
7
|
-
validates :name, presence: true, uniqueness: true, format: { with:
|
7
|
+
validates :name, presence: true, uniqueness: true, format: { with: Rabarber::Input::Roles::REGEX }
|
8
8
|
|
9
9
|
def self.names
|
10
10
|
pluck(:name).map(&:to_sym)
|
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.
|
19
|
-
rule = Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
18
|
+
def self.add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
|
19
|
+
rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
|
20
20
|
|
21
21
|
if action
|
22
22
|
instance.storage[:action_rules][controller] += [rule]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/railtie"
|
4
|
+
|
5
|
+
module Rabarber
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer "rabarber.after_initialize" do |app|
|
8
|
+
app.config.after_initialize do
|
9
|
+
Rabarber::Missing::Actions.new.handle
|
10
|
+
Rabarber::Missing::Roles.new.handle
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/rabarber/rule.rb
CHANGED
@@ -5,10 +5,10 @@ module Rabarber
|
|
5
5
|
attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
|
6
6
|
|
7
7
|
def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
|
8
|
-
@action =
|
9
|
-
@roles =
|
10
|
-
@dynamic_rule =
|
11
|
-
@negated_dynamic_rule =
|
8
|
+
@action = action
|
9
|
+
@roles = Array(roles)
|
10
|
+
@dynamic_rule = dynamic_rule
|
11
|
+
@negated_dynamic_rule = negated_dynamic_rule
|
12
12
|
end
|
13
13
|
|
14
14
|
def verify_access(user_roles, dynamic_rule_receiver, action_name = nil)
|
@@ -20,7 +20,7 @@ module Rabarber
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def roles_permitted?(user_roles)
|
23
|
-
return false if
|
23
|
+
return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
|
24
24
|
|
25
25
|
roles.empty? || (roles & user_roles).any?
|
26
26
|
end
|
@@ -44,19 +44,5 @@ module Rabarber
|
|
44
44
|
|
45
45
|
is_negated ? !result : result
|
46
46
|
end
|
47
|
-
|
48
|
-
def pre_process_action(action)
|
49
|
-
return action.to_sym if (action.is_a?(String) || action.is_a?(Symbol)) && action.present?
|
50
|
-
return action if action.nil?
|
51
|
-
|
52
|
-
raise InvalidArgumentError, "Action name must be a Symbol or a String"
|
53
|
-
end
|
54
|
-
|
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)
|
58
|
-
|
59
|
-
raise InvalidArgumentError, "Dynamic rule must be a Symbol, a String, or a Proc"
|
60
|
-
end
|
61
47
|
end
|
62
48
|
end
|
data/lib/rabarber/version.rb
CHANGED
data/lib/rabarber.rb
CHANGED
@@ -6,7 +6,17 @@ require_relative "rabarber/configuration"
|
|
6
6
|
require "active_record"
|
7
7
|
require "active_support"
|
8
8
|
|
9
|
-
require_relative "rabarber/
|
9
|
+
require_relative "rabarber/input/base"
|
10
|
+
require_relative "rabarber/input/actions"
|
11
|
+
require_relative "rabarber/input/dynamic_rules"
|
12
|
+
require_relative "rabarber/input/roles"
|
13
|
+
require_relative "rabarber/input/types/booleans"
|
14
|
+
require_relative "rabarber/input/types/procs"
|
15
|
+
require_relative "rabarber/input/types/symbols"
|
16
|
+
|
17
|
+
require_relative "rabarber/missing/base"
|
18
|
+
require_relative "rabarber/missing/actions"
|
19
|
+
require_relative "rabarber/missing/roles"
|
10
20
|
|
11
21
|
require_relative "rabarber/controllers/concerns/authorization"
|
12
22
|
require_relative "rabarber/helpers/helpers"
|
@@ -14,14 +24,16 @@ require_relative "rabarber/models/concerns/has_roles"
|
|
14
24
|
require_relative "rabarber/models/role"
|
15
25
|
require_relative "rabarber/permissions"
|
16
26
|
|
27
|
+
require_relative "rabarber/railtie"
|
28
|
+
|
17
29
|
module Rabarber
|
18
30
|
module_function
|
19
31
|
|
32
|
+
class Error < StandardError; end
|
33
|
+
class ConfigurationError < Rabarber::Error; end
|
34
|
+
class InvalidArgumentError < Rabarber::Error; end
|
35
|
+
|
20
36
|
def configure
|
21
|
-
yield(Configuration.instance)
|
37
|
+
yield(Rabarber::Configuration.instance)
|
22
38
|
end
|
23
|
-
|
24
|
-
class Error < StandardError; end
|
25
|
-
class ConfigurationError < Error; end
|
26
|
-
class InvalidArgumentError < Error; end
|
27
39
|
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: 1.
|
4
|
+
version: 1.2.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-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -42,10 +42,20 @@ files:
|
|
42
42
|
- lib/rabarber/configuration.rb
|
43
43
|
- lib/rabarber/controllers/concerns/authorization.rb
|
44
44
|
- lib/rabarber/helpers/helpers.rb
|
45
|
+
- lib/rabarber/input/actions.rb
|
46
|
+
- lib/rabarber/input/base.rb
|
47
|
+
- lib/rabarber/input/dynamic_rules.rb
|
48
|
+
- lib/rabarber/input/roles.rb
|
49
|
+
- lib/rabarber/input/types/booleans.rb
|
50
|
+
- lib/rabarber/input/types/procs.rb
|
51
|
+
- lib/rabarber/input/types/symbols.rb
|
52
|
+
- lib/rabarber/missing/actions.rb
|
53
|
+
- lib/rabarber/missing/base.rb
|
54
|
+
- lib/rabarber/missing/roles.rb
|
45
55
|
- lib/rabarber/models/concerns/has_roles.rb
|
46
56
|
- lib/rabarber/models/role.rb
|
47
57
|
- lib/rabarber/permissions.rb
|
48
|
-
- lib/rabarber/
|
58
|
+
- lib/rabarber/railtie.rb
|
49
59
|
- lib/rabarber/rule.rb
|
50
60
|
- lib/rabarber/version.rb
|
51
61
|
- rabarber.gemspec
|
data/lib/rabarber/role_names.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rabarber
|
4
|
-
module RoleNames
|
5
|
-
module_function
|
6
|
-
|
7
|
-
REGEX = /\A[a-z0-9_]+\z/
|
8
|
-
|
9
|
-
def pre_process(role_names)
|
10
|
-
return role_names.map(&:to_sym) if role_names.all? do |role_name|
|
11
|
-
(role_name.is_a?(Symbol) || role_name.is_a?(String)) && role_name.to_s.match?(REGEX)
|
12
|
-
end
|
13
|
-
|
14
|
-
raise(
|
15
|
-
InvalidArgumentError,
|
16
|
-
"Role names must be Symbols or Strings and may only contain lowercase letters, numbers and underscores"
|
17
|
-
)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|