rabarber 1.1.0 → 1.2.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 +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
|
[![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
|
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
|