rabarber 1.3.0 → 1.3.1

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: 77a15b7eb4164c74d70e81d34630d6051915acc2abe35896df8938d9cc53e473
4
- data.tar.gz: 847f675e00e9a85a158032af7ddc158f3189df78d4e6dae4ce15e2fe5024515e
3
+ metadata.gz: fc0447acdc988dc558859b695d24d227697258a92bb1f444944fb76bfbace526
4
+ data.tar.gz: 975f4377d5cc4fb2f28c42060012b67b3a85fea589aadb5106c33ecb943cde4a
5
5
  SHA512:
6
- metadata.gz: 7d16835ed84fd9ee2c2e7871080c5256bad34944ee0c1a3448c609f14732f1c7901539c0287bc5206feddb79ef77ee4aa001dd26a642d7c23b05498f5a668659
7
- data.tar.gz: 0bd35329a6608d4f6680b4aba7882d015df36f38e0b0d963c9df988edd40e490e67180e504b1901f05c957e27f8bffe585372f6494ccad2989310daaa7feec68
6
+ metadata.gz: cbfed2814ae750ec0b2870126818cb67c6b55c28a7529d8ab2db3364adc463768853b41ac89ba4653d014aa1024622ae261f5b2c8afcc10b99194fd916a40266
7
+ data.tar.gz: 7b77adeb8cc95acc4b104e6c39c6366b466262a8b0df0584d9e7d99d6369d96d01779a5962c7963696bccf7a941578a82774f2af23ac76d4a515bf37f7bed2ad
data/CHANGELOG.md CHANGED
@@ -1,7 +1,13 @@
1
+ ## 1.3.1
2
+
3
+ - Add `Rabarber::Role.assignees_for` method
4
+ - Fix inconsistent behavior where passing `nil` as a role name to role management methods would raise an `ActiveRecord` error instead of `Rabarber` error
5
+ - Various minor code improvements
6
+
1
7
  ## 1.3.0
2
8
 
3
9
  - Add methods to directly add, rename, and remove roles
4
- - `HasRoles#assign_roles` and `HasRoles#revoke_roles` methods now return the list of roles assigned to the user
10
+ - Modify `Rabarber::HasRoles#assign_roles` and `Rabarber::HasRoles#revoke_roles` methods to return the list of roles assigned to the user
5
11
  - Minor performance improvements
6
12
 
7
13
  ## 1.2.2
@@ -14,6 +20,7 @@
14
20
  - Cache roles to avoid unnecessary database queries
15
21
  - Introduce `cache_enabled` configuration option allowing to enable or disable role caching
16
22
  - Enhance the migration generator so that it can receive the table name of the model representing users in the application as an argument
23
+ - Fix an issue where an error would be raised if the user is not authenticated
17
24
  - Various minor improvements
18
25
 
19
26
  ## 1.2.0
@@ -42,7 +49,7 @@
42
49
  ## 1.0.2
43
50
 
44
51
  - Various enhancements for gem development and release
45
- - Modify `HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
52
+ - Modify `Rabarber::HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
46
53
 
47
54
  ## 1.0.1
48
55
 
@@ -61,7 +68,7 @@
61
68
 
62
69
  ## 0.1.4
63
70
 
64
- - Remove `HasRoles#role?` method as unnecessary
71
+ - Remove `Rabarber::HasRoles#role?` method as unnecessary
65
72
 
66
73
  ## 0.1.3
67
74
 
data/README.md CHANGED
@@ -3,9 +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 a role-based authorization library for Ruby on Rails, designed primarily for use in the web layer (specifically controllers and views) but not limited to that. It provides tools for managing user roles and defining authorization rules, mainly focusing on answering the question of 'Who can access which endpoint?'.
7
-
8
- Unlike some other libraries, Rabarber does not handle data scoping. Instead, it focuses on providing a lightweight and flexible solution for role-based access control, allowing developers to implement data scoping according to their specific business rules directly within their application's code.
6
+ Rabarber is a role-based authorization library for Ruby on Rails, designed primarily for use in the application's web layer (specifically controllers and views) but not limited to that. It provides tools for managing user roles and defining authorization rules and mainly focuses on answering the question: 'Who has access to which endpoints?'.
9
7
 
10
8
  ---
11
9
 
@@ -53,8 +51,6 @@ Next, generate a migration to create tables for storing roles in the database. M
53
51
  rails g rabarber:roles users
54
52
  ```
55
53
 
56
- This will create a migration file in `db/migrate` directory.
57
-
58
54
  Finally, run the migration to apply the changes to the database:
59
55
 
60
56
  ```
@@ -63,7 +59,7 @@ rails db:migrate
63
59
 
64
60
  ## Configuration
65
61
 
66
- Rabarber can be configured by using `.configure` method in an initializer:
62
+ If specific customization is required, Rabarber can be configured by using `.configure` method in an initializer:
67
63
 
68
64
  ```rb
69
65
  Rabarber.configure do |config|
@@ -76,17 +72,17 @@ Rabarber.configure do |config|
76
72
  end
77
73
  ```
78
74
 
79
- - `cache_enabled` must be a boolean determining whether roles are cached. Roles are cached by default to avoid unnecessary database queries. If you want to disable caching, set this option to `false`. If caching is enabled and you need to clear the cache, use `Rabarber::Cache.clear` method.
75
+ - `cache_enabled` must be a boolean determining whether roles are cached. _Roles are cached by default to avoid unnecessary database queries._ If you want to disable caching, set this option to `false`. If caching is enabled and you need to clear the cache, use `Rabarber::Cache.clear` method.
80
76
 
81
- - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. The default value is `:current_user`.
77
+ - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. _The default value is `:current_user`._
82
78
 
83
- - `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
+ - `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)._
84
80
 
85
- - `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.
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 symbols e.g., `[:index]`, `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._
86
82
 
87
- - `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.
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 symbols e.g., `[:admin]`, `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._
88
84
 
89
- - `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.
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._
90
86
 
91
87
  ## Roles
92
88
 
@@ -103,7 +99,7 @@ This adds the following methods:
103
99
 
104
100
  **`#assign_roles`**
105
101
 
106
- To assign roles to the user, use:
102
+ To assign roles, use:
107
103
 
108
104
  ```rb
109
105
  user.assign_roles(:accountant, :marketer)
@@ -166,7 +162,7 @@ Rabarber::Role.rename(:admin, :administrator)
166
162
  ```
167
163
  The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If a role with the new name already exists, it will return `false`.
168
164
 
169
- The method won't rename the role if it is assigned to any user. To force the rename, use the method with `force: true` argument:
165
+ The method won't rename the role and will return `false` if it is assigned to any user. To force the rename, use the method with `force: true` argument:
170
166
  ```rb
171
167
  Rabarber::Role.rename(:admin, :administrator, force: true)
172
168
  ```
@@ -181,7 +177,7 @@ Rabarber::Role.remove(:admin)
181
177
 
182
178
  This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
183
179
 
184
- The method won't remove the role if it is assigned to any user. To force the removal, use the method with `force: true` argument:
180
+ The method won't remove the role and will return `false` if it is assigned to any user. To force the removal, use the method with `force: true` argument:
185
181
  ```rb
186
182
  Rabarber::Role.remove(:admin, force: true)
187
183
  ```
@@ -194,9 +190,17 @@ If you need to list all the role names available in your application, use:
194
190
  Rabarber::Role.names
195
191
  ```
196
192
 
193
+ **`.assignees_for`**
194
+
195
+ To get all the users to whom the role is assigned, use:
196
+
197
+ ```rb
198
+ Rabarber::Role.assignees_for(:admin)
199
+ ```
200
+
197
201
  ## Authorization Rules
198
202
 
199
- 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.
203
+ Include `Rabarber::Authorization` module into the controller that needs authorization rules to be applied. Typically, it is `ApplicationController`, but it can be any controller of your choice.
200
204
 
201
205
  ```rb
202
206
  class ApplicationController < ActionController::Base
@@ -264,9 +268,9 @@ class InvoicesController < ApplicationController
264
268
  end
265
269
  ```
266
270
 
267
- This allows everyone to access `OrdersController` and its children and `index` action in `InvoicesController`. This also extends to scenarios where there is no user present, i.e. when the method responsible for returning the currently authenticated user in your application returns `nil`.
271
+ This allows everyone to access `OrdersController` and its children and also `index` action in `InvoicesController`. This extends to scenarios where there is no user present, i.e. when the method responsible for returning the currently authenticated user in your application returns `nil`.
268
272
 
269
- Be aware that if the user is not authenticated (the method responsible for returning the currently authenticated user in your application returns `nil`), Rabarber will treat this situation as if the user with no roles assigned was authenticated.
273
+ _Be aware that if the user is not authenticated (the method responsible for returning the currently authenticated user in your application returns `nil`), Rabarber will treat this situation as if the user with no roles assigned was authenticated._
270
274
 
271
275
  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 access anything.
272
276
 
@@ -301,7 +305,7 @@ class InvoicesController < ApplicationController
301
305
  end
302
306
  end
303
307
  ```
304
- 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.
308
+ You can pass a dynamic rule as `if` or `unless` argument. It can be a symbol, in which case the method with the same name will be called. Alternatively, it can be a proc, which will be executed within the context of the controller's instance.
305
309
 
306
310
  Rules defined in child classes don't override parent rules but rather add to them:
307
311
  ```rb
@@ -319,7 +323,7 @@ This means that `Crm::InvoicesController` is still accessible to `admin` but is
319
323
 
320
324
  ## View Helpers
321
325
 
322
- Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. To use them, simply include `Rabarber::Helpers` in the desired helper (usually `ApplicationHelper`, but it can be any helper):
326
+ Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. To use them, simply include `Rabarber::Helpers` in the desired helper. Usually it is `ApplicationHelper`, but it can be any helper of your choice.
323
327
 
324
328
  ```rb
325
329
  module ApplicationHelper
@@ -344,14 +348,18 @@ The usage is straightforward:
344
348
 
345
349
  ## Problems?
346
350
 
347
- Encountered a bug or facing a problem?
351
+ Facing a problem or want to suggest an enhancement?
352
+
353
+ - **Open a Discussion**: If you have a question, experience difficulties using the gem, or have an improvement suggestion, feel free to use the Discussions section.
354
+
355
+ Encountered a bug?
348
356
 
349
- - **Create an Issue**: If you've identified a problem, please create an issue on the gem's GitHub repository. Be sure to provide detailed information about the problem, including the steps to reproduce it.
357
+ - **Create an Issue**: If you've identified a bug, please create an issue. Be sure to provide detailed information about the problem, including the steps to reproduce it.
350
358
  - **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
351
359
 
352
360
  ## Contributing
353
361
 
354
- If you want to contribute, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
362
+ Before opening an issue or creating a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
355
363
 
356
364
  ## License
357
365
 
@@ -19,37 +19,37 @@ module Rabarber
19
19
  end
20
20
 
21
21
  def cache_enabled=(value)
22
- @cache_enabled = Rabarber::Input::Types::Booleans.new(
22
+ @cache_enabled = Rabarber::Input::Types::Boolean.new(
23
23
  value, Rabarber::ConfigurationError, "Configuration 'cache_enabled' must be a Boolean"
24
24
  ).process
25
25
  end
26
26
 
27
27
  def current_user_method=(method_name)
28
- @current_user_method = Rabarber::Input::Types::Symbols.new(
28
+ @current_user_method = Rabarber::Input::Types::Symbol.new(
29
29
  method_name, Rabarber::ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
30
30
  ).process
31
31
  end
32
32
 
33
33
  def must_have_roles=(value)
34
- @must_have_roles = Rabarber::Input::Types::Booleans.new(
34
+ @must_have_roles = Rabarber::Input::Types::Boolean.new(
35
35
  value, Rabarber::ConfigurationError, "Configuration 'must_have_roles' must be a Boolean"
36
36
  ).process
37
37
  end
38
38
 
39
39
  def when_actions_missing=(callable)
40
- @when_actions_missing = Rabarber::Input::Types::Procs.new(
40
+ @when_actions_missing = Rabarber::Input::Types::Proc.new(
41
41
  callable, Rabarber::ConfigurationError, "Configuration 'when_actions_missing' must be a Proc"
42
42
  ).process
43
43
  end
44
44
 
45
45
  def when_roles_missing=(callable)
46
- @when_roles_missing = Rabarber::Input::Types::Procs.new(
46
+ @when_roles_missing = Rabarber::Input::Types::Proc.new(
47
47
  callable, Rabarber::ConfigurationError, "Configuration 'when_roles_missing' must be a Proc"
48
48
  ).process
49
49
  end
50
50
 
51
51
  def when_unauthorized=(callable)
52
- @when_unauthorized = Rabarber::Input::Types::Procs.new(
52
+ @when_unauthorized = Rabarber::Input::Types::Proc.new(
53
53
  callable, Rabarber::ConfigurationError, "Configuration 'when_unauthorized' must be a Proc"
54
54
  ).process
55
55
  end
@@ -12,12 +12,12 @@ 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
- Rabarber::Permissions.add(
15
+ Rabarber::Core::Permissions.add(
16
16
  self,
17
- Rabarber::Input::Actions.new(action).process,
17
+ Rabarber::Input::Action.new(action).process,
18
18
  Rabarber::Input::Roles.new(roles).process,
19
- Rabarber::Input::DynamicRules.new(dynamic_rule).process,
20
- Rabarber::Input::DynamicRules.new(negated_dynamic_rule).process
19
+ Rabarber::Input::DynamicRule.new(dynamic_rule).process,
20
+ Rabarber::Input::DynamicRule.new(negated_dynamic_rule).process
21
21
  )
22
22
  end
23
23
  end
@@ -28,7 +28,7 @@ module Rabarber
28
28
  Rabarber::Missing::Actions.new(self.class).handle
29
29
  Rabarber::Missing::Roles.new(self.class).handle
30
30
 
31
- return if Rabarber::Permissions.access_granted?(rabarber_roles, self.class, action_name.to_sym, self)
31
+ return if Rabarber::Core::Permissions.access_granted?(rabarber_roles, self.class, action_name.to_sym, self)
32
32
 
33
33
  Rabarber::Configuration.instance.when_unauthorized.call(self)
34
34
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Core
5
+ module Access
6
+ def access_granted?(roles, controller, action, dynamic_rule_receiver)
7
+ controller_accessible?(roles, controller, dynamic_rule_receiver) ||
8
+ action_accessible?(roles, controller, action, dynamic_rule_receiver)
9
+ end
10
+
11
+ def controller_accessible?(roles, controller, dynamic_rule_receiver)
12
+ accessible_controllers(roles, dynamic_rule_receiver).any? do |accessible_controller|
13
+ controller <= accessible_controller
14
+ end
15
+ end
16
+
17
+ def action_accessible?(roles, controller, action, dynamic_rule_receiver)
18
+ action_rules[controller].any? { |rule| rule.verify_access(roles, dynamic_rule_receiver, action) }
19
+ end
20
+
21
+ private
22
+
23
+ def accessible_controllers(roles, dynamic_rule_receiver)
24
+ controller_rules.select { |_, rule| rule.verify_access(roles, dynamic_rule_receiver) }.keys
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "access"
4
+ require_relative "rule"
5
+
6
+ module Rabarber
7
+ module Core
8
+ class Permissions
9
+ include Singleton
10
+
11
+ extend Access
12
+
13
+ attr_reader :storage
14
+
15
+ def initialize
16
+ @storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
17
+ end
18
+
19
+ class << self
20
+ def add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
21
+ rule = Rabarber::Core::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
22
+
23
+ if action
24
+ instance.storage[:action_rules][controller] += [rule]
25
+ else
26
+ instance.storage[:controller_rules][controller] = rule
27
+ end
28
+ end
29
+
30
+ def controller_rules
31
+ instance.storage[:controller_rules]
32
+ end
33
+
34
+ def action_rules
35
+ instance.storage[:action_rules]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Core
5
+ class Rule
6
+ attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
7
+
8
+ def initialize(action, roles, dynamic_rule, negated_dynamic_rule)
9
+ @action = action
10
+ @roles = Array(roles)
11
+ @dynamic_rule = dynamic_rule
12
+ @negated_dynamic_rule = negated_dynamic_rule
13
+ end
14
+
15
+ def verify_access(user_roles, dynamic_rule_receiver, action_name = nil)
16
+ action_accessible?(action_name) && roles_permitted?(user_roles) && dynamic_rule_followed?(dynamic_rule_receiver)
17
+ end
18
+
19
+ def action_accessible?(action_name)
20
+ action_name.nil? || action_name == action
21
+ end
22
+
23
+ def roles_permitted?(user_roles)
24
+ return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
25
+
26
+ roles.empty? || (roles & user_roles).any?
27
+ end
28
+
29
+ def dynamic_rule_followed?(dynamic_rule_receiver)
30
+ !!(execute_dynamic_rule(dynamic_rule_receiver, false) && execute_dynamic_rule(dynamic_rule_receiver, true))
31
+ end
32
+
33
+ private
34
+
35
+ def execute_dynamic_rule(dynamic_rule_receiver, is_negated)
36
+ rule = is_negated ? negated_dynamic_rule : dynamic_rule
37
+
38
+ return true if rule.nil?
39
+
40
+ result = if rule.is_a?(Proc)
41
+ dynamic_rule_receiver.instance_exec(&rule)
42
+ else
43
+ dynamic_rule_receiver.send(rule)
44
+ end
45
+
46
+ is_negated ? !result : result
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Input
5
+ class Action < Rabarber::Input::Base
6
+ def valid?
7
+ Rabarber::Input::Types::Symbol.new(value).valid? || value.nil?
8
+ end
9
+
10
+ private
11
+
12
+ def processed_value
13
+ case value
14
+ when String, Symbol
15
+ value.to_sym
16
+ when nil
17
+ value
18
+ end
19
+ end
20
+
21
+ def default_error_message
22
+ "Action name must be a Symbol or a String"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -5,7 +5,7 @@ module Rabarber
5
5
  class Base
6
6
  attr_reader :value, :error_type, :error_message
7
7
 
8
- def initialize(value, error_type, error_message)
8
+ def initialize(value, error_type = Rabarber::InvalidArgumentError, error_message = default_error_message)
9
9
  @value = value
10
10
  @error_type = error_type
11
11
  @error_message = error_message
@@ -15,16 +15,20 @@ module Rabarber
15
15
  valid? ? processed_value : raise_error
16
16
  end
17
17
 
18
- private
19
-
20
18
  def valid?
21
19
  raise NotImplementedError
22
20
  end
23
21
 
22
+ private
23
+
24
24
  def processed_value
25
25
  raise NotImplementedError
26
26
  end
27
27
 
28
+ def default_error_message
29
+ raise NotImplementedError
30
+ end
31
+
28
32
  def raise_error
29
33
  raise error_type, error_message
30
34
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Input
5
+ class DynamicRule < Rabarber::Input::Base
6
+ def valid?
7
+ Rabarber::Input::Types::Symbol.new(value).valid? || Rabarber::Input::Types::Proc.new(value).valid? || value.nil?
8
+ end
9
+
10
+ private
11
+
12
+ def processed_value
13
+ case value
14
+ when String, Symbol
15
+ value.to_sym
16
+ when Proc, nil
17
+ value
18
+ end
19
+ end
20
+
21
+ def default_error_message
22
+ "Dynamic rule must be a Symbol, a String, or a Proc"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Input
5
+ class Role < Rabarber::Input::Base
6
+ REGEX = /\A[a-z0-9_]+\z/
7
+
8
+ def valid?
9
+ Rabarber::Input::Types::Symbol.new(value).valid? && value.to_s.match?(REGEX)
10
+ end
11
+
12
+ private
13
+
14
+ def processed_value
15
+ value.to_sym
16
+ end
17
+
18
+ def default_error_message
19
+ "Role name must be a Symbol or a String and may only contain lowercase letters, numbers and underscores"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,30 +3,23 @@
3
3
  module Rabarber
4
4
  module Input
5
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
6
  def value
18
7
  Array(super)
19
8
  end
20
9
 
21
- private
22
-
23
10
  def valid?
24
- value.all? { |role_name| (role_name.is_a?(Symbol) || role_name.is_a?(String)) && role_name.to_s.match?(REGEX) }
11
+ value.all? { |role_name| Rabarber::Input::Role.new(role_name).valid? }
25
12
  end
26
13
 
14
+ private
15
+
27
16
  def processed_value
28
17
  value.map(&:to_sym)
29
18
  end
19
+
20
+ def default_error_message
21
+ "Role names must be Symbols or Strings and may only contain lowercase letters, numbers and underscores"
22
+ end
30
23
  end
31
24
  end
32
25
  end
@@ -3,16 +3,20 @@
3
3
  module Rabarber
4
4
  module Input
5
5
  module Types
6
- class Booleans < Rabarber::Input::Base
7
- private
8
-
6
+ class Boolean < Rabarber::Input::Base
9
7
  def valid?
10
8
  [true, false].include?(value)
11
9
  end
12
10
 
11
+ private
12
+
13
13
  def processed_value
14
14
  value
15
15
  end
16
+
17
+ def default_error_message
18
+ "Value must be a Boolean"
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -3,16 +3,20 @@
3
3
  module Rabarber
4
4
  module Input
5
5
  module Types
6
- class Procs < Rabarber::Input::Base
7
- private
8
-
6
+ class Proc < Rabarber::Input::Base
9
7
  def valid?
10
- value.is_a?(Proc)
8
+ value.is_a?(::Proc)
11
9
  end
12
10
 
11
+ private
12
+
13
13
  def processed_value
14
14
  value
15
15
  end
16
+
17
+ def default_error_message
18
+ "Value must be a Proc"
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -3,16 +3,20 @@
3
3
  module Rabarber
4
4
  module Input
5
5
  module Types
6
- class Symbols < Rabarber::Input::Base
7
- private
8
-
6
+ class Symbol < Rabarber::Input::Base
9
7
  def valid?
10
- (value.is_a?(Symbol) || value.is_a?(String)) && value.present?
8
+ (value.is_a?(::Symbol) || value.is_a?(String)) && value.present?
11
9
  end
12
10
 
11
+ private
12
+
13
13
  def processed_value
14
14
  value.to_sym
15
15
  end
16
+
17
+ def default_error_message
18
+ "Value must be a Symbol or a String"
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -41,17 +41,17 @@ module Rabarber
41
41
 
42
42
  def controller_rules
43
43
  if controller
44
- Rabarber::Permissions.controller_rules.slice(controller)
44
+ Rabarber::Core::Permissions.controller_rules.slice(controller)
45
45
  else
46
- Rabarber::Permissions.controller_rules
46
+ Rabarber::Core::Permissions.controller_rules
47
47
  end
48
48
  end
49
49
 
50
50
  def action_rules
51
51
  if controller
52
- Rabarber::Permissions.action_rules.slice(controller)
52
+ Rabarber::Core::Permissions.action_rules.slice(controller)
53
53
  else
54
- Rabarber::Permissions.action_rules
54
+ Rabarber::Core::Permissions.action_rules
55
55
  end
56
56
  end
57
57
  end
@@ -52,6 +52,11 @@ module Rabarber
52
52
  roles
53
53
  end
54
54
 
55
+ def roleable_class
56
+ @@included.constantize
57
+ end
58
+ module_function :roleable_class
59
+
55
60
  private
56
61
 
57
62
  def create_new_roles(role_names)
@@ -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: Rabarber::Input::Roles::REGEX }
7
+ validates :name, presence: true, uniqueness: true, format: { with: Rabarber::Input::Role::REGEX }, strict: true
8
8
 
9
9
  has_and_belongs_to_many :roleables, join_table: "rabarber_roles_roleables"
10
10
 
@@ -46,6 +46,12 @@ module Rabarber
46
46
  !!role.destroy!
47
47
  end
48
48
 
49
+ def assignees_for(name)
50
+ Rabarber::HasRoles.roleable_class.joins(:rabarber_roles).where(
51
+ rabarber_roles: { name: Rabarber::Input::Role.new(name).process }
52
+ )
53
+ end
54
+
49
55
  private
50
56
 
51
57
  def delete_roles_cache
@@ -64,7 +70,7 @@ module Rabarber
64
70
  end
65
71
 
66
72
  def process_role_name(name)
67
- Rabarber::Input::Roles.new(name).process[0]
73
+ Rabarber::Input::Role.new(name).process
68
74
  end
69
75
  end
70
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "1.3.0"
4
+ VERSION = "1.3.1"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -8,12 +8,13 @@ require "active_record"
8
8
  require "active_support"
9
9
 
10
10
  require_relative "rabarber/input/base"
11
- require_relative "rabarber/input/actions"
12
- require_relative "rabarber/input/dynamic_rules"
11
+ require_relative "rabarber/input/action"
12
+ require_relative "rabarber/input/dynamic_rule"
13
+ require_relative "rabarber/input/role"
13
14
  require_relative "rabarber/input/roles"
14
- require_relative "rabarber/input/types/booleans"
15
- require_relative "rabarber/input/types/procs"
16
- require_relative "rabarber/input/types/symbols"
15
+ require_relative "rabarber/input/types/boolean"
16
+ require_relative "rabarber/input/types/proc"
17
+ require_relative "rabarber/input/types/symbol"
17
18
 
18
19
  require_relative "rabarber/missing/base"
19
20
  require_relative "rabarber/missing/actions"
@@ -25,7 +26,8 @@ require_relative "rabarber/controllers/concerns/authorization"
25
26
  require_relative "rabarber/helpers/helpers"
26
27
  require_relative "rabarber/models/concerns/has_roles"
27
28
  require_relative "rabarber/models/role"
28
- require_relative "rabarber/permissions"
29
+
30
+ require_relative "rabarber/core/permissions"
29
31
 
30
32
  require_relative "rabarber/railtie"
31
33
 
data/rabarber.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["enjaku4", "trafium"]
9
9
  spec.email = ["rabarber_gem@icloud.com"]
10
10
 
11
- spec.summary = "Simple authorization library for Ruby on Rails."
11
+ spec.summary = "Simple role-based authorization library for Ruby on Rails."
12
12
  spec.homepage = "https://github.com/enjaku4/rabarber"
13
13
  spec.license = "MIT"
14
14
  spec.required_ruby_version = ">= 3.0"
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.3.0
4
+ version: 1.3.1
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-02-20 00:00:00.000000000 Z
12
+ date: 2024-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -38,27 +38,28 @@ files:
38
38
  - lib/generators/rabarber/roles_generator.rb
39
39
  - lib/generators/rabarber/templates/create_rabarber_roles.rb.erb
40
40
  - lib/rabarber.rb
41
- - lib/rabarber/access.rb
42
41
  - lib/rabarber/cache.rb
43
42
  - lib/rabarber/configuration.rb
44
43
  - lib/rabarber/controllers/concerns/authorization.rb
44
+ - lib/rabarber/core/access.rb
45
+ - lib/rabarber/core/permissions.rb
46
+ - lib/rabarber/core/rule.rb
45
47
  - lib/rabarber/helpers/helpers.rb
46
- - lib/rabarber/input/actions.rb
48
+ - lib/rabarber/input/action.rb
47
49
  - lib/rabarber/input/base.rb
48
- - lib/rabarber/input/dynamic_rules.rb
50
+ - lib/rabarber/input/dynamic_rule.rb
51
+ - lib/rabarber/input/role.rb
49
52
  - lib/rabarber/input/roles.rb
50
- - lib/rabarber/input/types/booleans.rb
51
- - lib/rabarber/input/types/procs.rb
52
- - lib/rabarber/input/types/symbols.rb
53
+ - lib/rabarber/input/types/boolean.rb
54
+ - lib/rabarber/input/types/proc.rb
55
+ - lib/rabarber/input/types/symbol.rb
53
56
  - lib/rabarber/logger.rb
54
57
  - lib/rabarber/missing/actions.rb
55
58
  - lib/rabarber/missing/base.rb
56
59
  - lib/rabarber/missing/roles.rb
57
60
  - lib/rabarber/models/concerns/has_roles.rb
58
61
  - lib/rabarber/models/role.rb
59
- - lib/rabarber/permissions.rb
60
62
  - lib/rabarber/railtie.rb
61
- - lib/rabarber/rule.rb
62
63
  - lib/rabarber/version.rb
63
64
  - rabarber.gemspec
64
65
  homepage: https://github.com/enjaku4/rabarber
@@ -83,5 +84,5 @@ requirements: []
83
84
  rubygems_version: 3.3.26
84
85
  signing_key:
85
86
  specification_version: 4
86
- summary: Simple authorization library for Ruby on Rails.
87
+ summary: Simple role-based authorization library for Ruby on Rails.
87
88
  test_files: []
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rabarber
4
- module Access
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
- end
9
-
10
- def controller_accessible?(roles, controller, dynamic_rule_receiver)
11
- accessible_controllers(roles, dynamic_rule_receiver).any? do |accessible_controller|
12
- controller <= accessible_controller
13
- end
14
- end
15
-
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
- end
19
-
20
- private
21
-
22
- def accessible_controllers(roles, dynamic_rule_receiver)
23
- controller_rules.select { |_, rule| rule.verify_access(roles, dynamic_rule_receiver) }.keys
24
- end
25
- end
26
- end
@@ -1,30 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "access"
4
- require_relative "rule"
5
-
6
- module Rabarber
7
- class Permissions
8
- include Singleton
9
-
10
- extend Access
11
-
12
- attr_reader :storage
13
-
14
- def initialize
15
- @storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
16
- end
17
-
18
- class << self
19
- def add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
20
- rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
21
-
22
- if action
23
- instance.storage[:action_rules][controller] += [rule]
24
- else
25
- instance.storage[:controller_rules][controller] = rule
26
- end
27
- end
28
-
29
- def controller_rules
30
- instance.storage[:controller_rules]
31
- end
32
-
33
- def action_rules
34
- instance.storage[:action_rules]
35
- end
36
- end
37
- end
38
- end
data/lib/rabarber/rule.rb DELETED
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rabarber
4
- class Rule
5
- attr_reader :action, :roles, :dynamic_rule, :negated_dynamic_rule
6
-
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
12
- end
13
-
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)
16
- end
17
-
18
- def action_accessible?(action_name)
19
- action_name.nil? || action_name == action
20
- end
21
-
22
- def roles_permitted?(user_roles)
23
- return false if Rabarber::Configuration.instance.must_have_roles && user_roles.empty?
24
-
25
- roles.empty? || (roles & user_roles).any?
26
- end
27
-
28
- def dynamic_rule_followed?(dynamic_rule_receiver)
29
- !!(execute_dynamic_rule(dynamic_rule_receiver, false) && execute_dynamic_rule(dynamic_rule_receiver, true))
30
- end
31
-
32
- private
33
-
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
46
- end
47
- end
48
- end