rabarber 1.0.5 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e54ea7ddbb816cbc279edf4cb5d8863086fb452528f1403da9c944e4d23b862a
4
- data.tar.gz: a6cc1c88ced2c76561c368669e6413a476b7590fa105a9876a6dfcbd139c2b41
3
+ metadata.gz: e7eaa4eaef0f5c6d072e3a5b545b0ce152651df15b521be933aac78e1add85ba
4
+ data.tar.gz: d0557fcd900f2bebd58f6489c2f3f79c2f95c7c14bbeeeb59a294fdfcbcb3f49
5
5
  SHA512:
6
- metadata.gz: ae10eca6248824b4dab6a7038b813933ee9ecaab1fd1cce67ef07ed95fdb0a77f5f70b3138574e9b42ea5d570da2a56186845978950b1fda03240e836cb07f8f
7
- data.tar.gz: f8f4e00a8b76a8eb7a23e7ad7088ab38325e23ebd2ccab43b744def1d977b531f02866025c56f982578d3af8894582756bd980057159095a1bfe9eb3f6d261d5
6
+ metadata.gz: cadc08751bf39ab0c6eb8a357854fe00bb478533a7f5a7e0760929d6acedad0fcecb31e17c21c980c764d59302a569df74e847f09c778b2e3f1c257c520c4336
7
+ data.tar.gz: 7067e81820ce07c0929e3f18ad079c9cfa44b0dc473138b042e4e882b8db1cda17bbc680a431634c42695434a088605fd897ab4a2e25a4d4d2b9bac7c8180fd5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
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
+
6
+ ## 1.1.0
7
+
8
+ - Add support for `unless` argument in `grant_access` method, allowing to define negated dynamic rules
9
+ - Fix a bug where specifying a dynamic rule as a symbol without specifying an action would result in an error
10
+
1
11
  ## 1.0.5
2
12
 
3
13
  - 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 web layer of your application but not limited to that. It provides a set of useful tools for managing user roles and defining authorization rules.
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
 
10
10
  **Example of Usage**:
11
11
 
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.
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
 
@@ -63,20 +63,26 @@ rails db:migrate
63
63
 
64
64
  ## Configuration
65
65
 
66
- Rabarber can be configured by adding the following code into an initializer:
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.when_unauthorized = ->(controller) {
73
- controller.head 418
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
- - `when_unauthorized` must be a lambda where you can define your actions 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
+
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 are unique.
117
+ The role names must be unique.
112
118
 
113
119
  **`#revoke_roles`**
114
120
 
@@ -145,8 +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 custom UI for managing roles or assign necessary roles during migration or runtime (e.g., when the user is created). You can also write custom authorization policies based on `#has_role?` method (e.g., to scope the data that the user can access). Adapt these methods to fit the requirements of your application.
149
-
150
154
  ## Authorization Rules
151
155
 
152
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.
@@ -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 rules, Rabarber provides the following:
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 custom rule as `if` argument. It can be a symbol (the method with the same name will be called) or a lambda.
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.
246
260
 
247
- Rules defined in children don't override parent rules but rather add to them:
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
@@ -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, %i[role_id roleable_id], unique: true
15
+ add_index :rabarber_roles_roleables, [:role_id, :roleable_id], unique: true
16
16
  end
17
17
  end
@@ -2,25 +2,25 @@
2
2
 
3
3
  module Rabarber
4
4
  module Access
5
- def access_granted?(roles, controller, action, custom_rule_receiver)
6
- controller_accessible?(roles, controller, custom_rule_receiver) ||
7
- action_accessible?(roles, controller, action, custom_rule_receiver)
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, custom_rule_receiver)
11
- accessible_controllers(roles, custom_rule_receiver).any? do |accessible_controller|
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, custom_rule_receiver)
17
- action_rules[controller].any? { |rule| rule.verify_access(roles, custom_rule_receiver, action) }
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, custom_rule_receiver)
23
- controller_rules.select { |_, rule| rule.verify_access(roles, custom_rule_receiver) }.keys
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
@@ -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 = :current_user
13
- @must_have_roles = false
14
- @when_unauthorized = ->(controller) do
15
- if controller.request.format.html?
16
- controller.redirect_back fallback_location: controller.main_app.root_path
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
- unless (method_name.is_a?(Symbol) || method_name.is_a?(String)) && method_name.present?
25
- raise ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
26
- end
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
- raise ConfigurationError, "Configuration 'must_have_roles' must be a Boolean" unless [true, false].include?(value)
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
- @must_have_roles = value
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
- raise ConfigurationError, "Configuration 'when_unauthorized' must be a Proc" unless callable.is_a?(Proc)
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
- @when_unauthorized = callable
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
@@ -9,19 +9,30 @@ module Rabarber
9
9
  end
10
10
 
11
11
  class_methods do
12
- def grant_access(action: nil, roles: nil, if: nil)
13
- Permissions.write(self, action, roles, binding.local_variable_get(:if))
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
+ 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
+ )
14
22
  end
15
23
  end
16
24
 
17
25
  private
18
26
 
19
27
  def verify_access
20
- return if Permissions.access_granted?(
21
- send(::Rabarber::Configuration.instance.current_user_method).roles, self.class, action_name.to_sym, self
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
22
33
  )
23
34
 
24
- ::Rabarber::Configuration.instance.when_unauthorized.call(self)
35
+ Rabarber::Configuration.instance.when_unauthorized.call(self)
25
36
  end
26
37
  end
27
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(::Rabarber::Configuration.instance.current_user_method).has_role?(*roles)
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(::Rabarber::Configuration.instance.current_user_method).has_role?(*roles)
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 Procs < Rabarber::Input::Base
7
+ private
8
+
9
+ def valid?
10
+ value.is_a?(Proc)
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
- raise Error, "Rabarber::HasRoles can only be included once" if defined?(@@included) && @@included != name
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 & RoleNames.pre_process(role_names)).any?
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 = RoleNames.pre_process(role_names)
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: RoleNames.pre_process(role_names))
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
@@ -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: RoleNames::REGEX }
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)
@@ -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, custom_rule)
19
- rule = Rule.new(action, roles, custom_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
@@ -2,16 +2,17 @@
2
2
 
3
3
  module Rabarber
4
4
  class Rule
5
- attr_reader :action, :roles, :custom
5
+ attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
6
6
 
7
- def initialize(action, roles, custom)
8
- @action = pre_process_action(action)
9
- @roles = RoleNames.pre_process(Array(roles))
10
- @custom = pre_process_custom_rule(custom)
7
+ def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
8
+ @action = action
9
+ @roles = Array(roles)
10
+ @dynamic_rule = dynamic_rule
11
+ @negated_dynamic_rule = negated_dynamic_rule
11
12
  end
12
13
 
13
- def verify_access(user_roles, custom_rule_receiver, action_name = nil)
14
- action_accessible?(action_name) && roles_permitted?(user_roles) && custom_rule_followed?(custom_rule_receiver)
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)
@@ -19,37 +20,29 @@ module Rabarber
19
20
  end
20
21
 
21
22
  def roles_permitted?(user_roles)
22
- return false if ::Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
23
+ return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
23
24
 
24
25
  roles.empty? || (roles & user_roles).any?
25
26
  end
26
27
 
27
- def custom_rule_followed?(custom_rule_receiver)
28
- custom.nil? || execute_custom_rule(custom_rule_receiver)
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 execute_custom_rule(custom_rule_receiver)
34
- if custom.is_a?(Proc)
35
- custom_rule_receiver.instance_exec(&custom)
36
- else
37
- custom_rule_receiver.send(custom)
38
- end
39
- end
40
-
41
- def pre_process_action(action)
42
- return action.to_sym if (action.is_a?(String) || action.is_a?(Symbol)) && action.present?
43
- return action if action.nil?
34
+ def execute_dynamic_rule(dynamic_rule_receiver, is_negated)
35
+ rule = is_negated ? negated_dynamic_rule : dynamic_rule
44
36
 
45
- raise InvalidArgumentError, "Action name must be a Symbol or a String"
46
- end
37
+ return true if rule.nil?
47
38
 
48
- def pre_process_custom_rule(custom)
49
- return custom.to_sym if (custom.is_a?(String) || custom.is_a?(Symbol)) && action.present?
50
- return custom if custom.nil? || custom.is_a?(Proc)
39
+ result = if rule.is_a?(Proc)
40
+ dynamic_rule_receiver.instance_exec(&rule)
41
+ else
42
+ dynamic_rule_receiver.send(rule)
43
+ end
51
44
 
52
- raise InvalidArgumentError, "Custom rule must be a Symbol, a String, or a Proc"
45
+ is_negated ? !result : result
53
46
  end
54
47
  end
55
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "1.0.5"
4
+ VERSION = "1.2.0"
5
5
  end
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/role_names"
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.0.5
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-10 00:00:00.000000000 Z
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/role_names.rb
58
+ - lib/rabarber/railtie.rb
49
59
  - lib/rabarber/rule.rb
50
60
  - lib/rabarber/version.rb
51
61
  - rabarber.gemspec
@@ -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