rabarber 1.3.0 → 1.4.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: 77a15b7eb4164c74d70e81d34630d6051915acc2abe35896df8938d9cc53e473
4
- data.tar.gz: 847f675e00e9a85a158032af7ddc158f3189df78d4e6dae4ce15e2fe5024515e
3
+ metadata.gz: bcddaf07627eb382c58a733150e460348527476d2234f65fe0db2760bce6206f
4
+ data.tar.gz: 3d1bbfab2a57d860bcb9656cadb5606d09674f887a58b96db0897c3516aed5d2
5
5
  SHA512:
6
- metadata.gz: 7d16835ed84fd9ee2c2e7871080c5256bad34944ee0c1a3448c609f14732f1c7901539c0287bc5206feddb79ef77ee4aa001dd26a642d7c23b05498f5a668659
7
- data.tar.gz: 0bd35329a6608d4f6680b4aba7882d015df36f38e0b0d963c9df988edd40e490e67180e504b1901f05c957e27f8bffe585372f6494ccad2989310daaa7feec68
6
+ metadata.gz: e5ae51e17b580757cc427f269b43155ca3ea5eda5b4813be5d023c273f2271d96108639e24c1921ace823de4cc06b43c03372eb98d64f6d4376f9628e7b6d616
7
+ data.tar.gz: 232a9ded49264955b8cf7d22de5a1443947dd84d90f44f15f6dc9c9346ee7b1cee56afa85a55bf44bf5325bb1d26e17e6fcf0b8076d3f14381a9bb43254038ab
data/CHANGELOG.md CHANGED
@@ -1,7 +1,19 @@
1
+ ## 1.4.0
2
+
3
+ - Add 'Audit trail' feature: Logging of role assignments, revocations, and unauthorized access attempts
4
+ - Add `audit_trail_enabled` configuration option, allowing to enable or disable the audit trail
5
+ - Deprecate `when_actions_missing` and `when_roles_missing` configuration options (see [the discussion](https://github.com/enjaku4/rabarber/discussions/48))
6
+
7
+ ## 1.3.1
8
+
9
+ - Add `Rabarber::Role.assignees_for` method
10
+ - Fix inconsistent behavior where passing `nil` as a role name to role management methods would raise an `ActiveRecord` error instead of `Rabarber` error
11
+ - Various minor code improvements
12
+
1
13
  ## 1.3.0
2
14
 
3
15
  - 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
16
+ - Modify `Rabarber::HasRoles#assign_roles` and `Rabarber::HasRoles#revoke_roles` methods to return the list of roles assigned to the user
5
17
  - Minor performance improvements
6
18
 
7
19
  ## 1.2.2
@@ -14,6 +26,7 @@
14
26
  - Cache roles to avoid unnecessary database queries
15
27
  - Introduce `cache_enabled` configuration option allowing to enable or disable role caching
16
28
  - Enhance the migration generator so that it can receive the table name of the model representing users in the application as an argument
29
+ - Fix an issue where an error would be raised if the user is not authenticated
17
30
  - Various minor improvements
18
31
 
19
32
  ## 1.2.0
@@ -42,7 +55,7 @@
42
55
  ## 1.0.2
43
56
 
44
57
  - Various enhancements for gem development and release
45
- - Modify `HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
58
+ - Modify `Rabarber::HasRoles#roles` method to return an array of role names instead of `Rabarber::Role` objects
46
59
 
47
60
  ## 1.0.1
48
61
 
@@ -61,7 +74,7 @@
61
74
 
62
75
  ## 0.1.4
63
76
 
64
- - Remove `HasRoles#role?` method as unnecessary
77
+ - Remove `Rabarber::HasRoles#role?` method as unnecessary
65
78
 
66
79
  ## 0.1.3
67
80
 
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, primarily designed for use in the web layer of your application but not limited to that. It provides a set of tools for managing user roles and defining authorization rules, along with audit logging for enhanced security.
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,30 +59,37 @@ 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|
66
+ config.audit_trail_enabled = true
70
67
  config.cache_enabled = true
71
68
  config.current_user_method = :current_user
72
69
  config.must_have_roles = false
73
- config.when_actions_missing = -> (missing_actions, context) { ... }
74
- config.when_roles_missing = -> (missing_roles, context) { ... }
75
- config.when_unauthorized = -> (controller) { ... }
70
+ config.when_unauthorized = -> (controller) {
71
+ if controller.request.format.html?
72
+ controller.redirect_back fallback_location: controller.root_path
73
+ else
74
+ controller.head :unauthorized
75
+ end
76
+ }
76
77
  end
77
78
  ```
78
79
 
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.
80
+ - `audit_trail_enabled` must be a boolean determining whether the audit trail functionality is enabled. _The audit trail is enabled by default._
81
+ - `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.
82
+ - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. _The default value is `:current_user`._
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)._
84
+ - `when_unauthorized` must be a proc where you can define the behaviour when access is not authorized. Lambda argument `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
85
 
81
- - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. The default value is `:current_user`.
86
+ ### Deprecated Configuration Options
82
87
 
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).
88
+ The following configuration options are deprecated and will be removed in the next major version (see [the discussion](https://github.com/enjaku4/rabarber/discussions/48)):
84
89
 
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.
90
+ - `when_actions_missing` must be a proc where you can define the behaviour when the action specified in `grant_access` method cannot be found in the controller. Lambda argument `missing_actions` is an array of symbols, e.g., `[:index]`, while `context` argument is a hash that looks like this: `{ controller: "InvoicesController" }`. This check is performed when the application is initialized if `eager_load` configuration is enabled in Rails and also on every request. _By default, an error is raised when action is missing._
86
91
 
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.
88
-
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.
92
+ - `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. Lambda argument `missing_roles` is an array of symbols, e.g., `[:admin]`, while `context` argument is a hash that looks like this: `{ controller: "InvoicesController", action: "index" }`. This check is performed when the application is initialized if `eager_load` configuration is enabled in Rails and also on every request. _By default, a warning is logged when roles are missing._
90
93
 
91
94
  ## Roles
92
95
 
@@ -101,20 +104,20 @@ end
101
104
 
102
105
  This adds the following methods:
103
106
 
104
- **`#assign_roles`**
107
+ **`#assign_roles(*roles, create_new: true)`**
105
108
 
106
- To assign roles to the user, use:
109
+ To assign roles, use:
107
110
 
108
111
  ```rb
109
112
  user.assign_roles(:accountant, :marketer)
110
113
  ```
111
- By default, `#assign_roles` method will automatically create any roles that don't exist. If you want to assign only existing roles and prevent the creation of new ones, use the method with `create_new: false` argument:
114
+ By default, it will automatically create any roles that don't exist. If you want to assign only existing roles and prevent the creation of new ones, use the method with `create_new: false` argument:
112
115
  ```rb
113
116
  user.assign_roles(:accountant, :marketer, create_new: false)
114
117
  ```
115
118
  The method returns an array of roles assigned to the user.
116
119
 
117
- **`#revoke_roles`**
120
+ **`#revoke_roles(*roles)`**
118
121
 
119
122
  To revoke roles, use:
120
123
 
@@ -125,7 +128,7 @@ If any of the specified roles doesn't exist or the user doesn't have the role yo
125
128
 
126
129
  The method returns an array of roles assigned to the user.
127
130
 
128
- **`#has_role?`**
131
+ **`#has_role?(*roles)`**
129
132
 
130
133
  To check whether the user has a role, use:
131
134
 
@@ -147,7 +150,7 @@ user.roles
147
150
 
148
151
  To manipulate roles directly, you can use `Rabarber::Role` methods:
149
152
 
150
- **`.add`**
153
+ **`.add(role)`**
151
154
 
152
155
  To add a new role, use:
153
156
 
@@ -157,7 +160,7 @@ Rabarber::Role.add(:admin)
157
160
 
158
161
  This will create a new role with the specified name and return `true`. If the role already exists, it will return `false`.
159
162
 
160
- **`.rename`**
163
+ **`.rename(old_role_name, new_role_name, force: false)`**
161
164
 
162
165
  To rename a role, use:
163
166
 
@@ -166,12 +169,12 @@ Rabarber::Role.rename(:admin, :administrator)
166
169
  ```
167
170
  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
171
 
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:
172
+ 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
173
  ```rb
171
174
  Rabarber::Role.rename(:admin, :administrator, force: true)
172
175
  ```
173
176
 
174
- **`.remove`**
177
+ **`.remove(role, force: false)`**
175
178
 
176
179
  To remove a role, use:
177
180
 
@@ -181,7 +184,7 @@ Rabarber::Role.remove(:admin)
181
184
 
182
185
  This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
183
186
 
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:
187
+ 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
188
  ```rb
186
189
  Rabarber::Role.remove(:admin, force: true)
187
190
  ```
@@ -194,9 +197,17 @@ If you need to list all the role names available in your application, use:
194
197
  Rabarber::Role.names
195
198
  ```
196
199
 
200
+ **`.assignees_for(role)`**
201
+
202
+ To get all the users to whom the role is assigned, use:
203
+
204
+ ```rb
205
+ Rabarber::Role.assignees_for(:admin)
206
+ ```
207
+
197
208
  ## Authorization Rules
198
209
 
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.
210
+ 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
211
 
201
212
  ```rb
202
213
  class ApplicationController < ActionController::Base
@@ -204,7 +215,7 @@ class ApplicationController < ActionController::Base
204
215
  ...
205
216
  end
206
217
  ```
207
- This adds `.grant_access` method which allows you to define the authorization rules.
218
+ This adds `.grant_access(action: nil, roles: nil, if: nil, unless: nil)` method which allows you to define the authorization rules.
208
219
 
209
220
  The most basic usage of the method is as follows:
210
221
 
@@ -264,9 +275,9 @@ class InvoicesController < ApplicationController
264
275
  end
265
276
  ```
266
277
 
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`.
278
+ 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
279
 
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.
280
+ _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
281
 
271
282
  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
283
 
@@ -301,7 +312,7 @@ class InvoicesController < ApplicationController
301
312
  end
302
313
  end
303
314
  ```
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.
315
+ 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
316
 
306
317
  Rules defined in child classes don't override parent rules but rather add to them:
307
318
  ```rb
@@ -319,7 +330,7 @@ This means that `Crm::InvoicesController` is still accessible to `admin` but is
319
330
 
320
331
  ## View Helpers
321
332
 
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):
333
+ Rabarber also provides a couple of helpers that can be used in views: `visible_to(*roles, &block)` and `hidden_from(*roles, &block)`. 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
334
 
324
335
  ```rb
325
336
  module ApplicationHelper
@@ -342,16 +353,30 @@ The usage is straightforward:
342
353
  <% end %>
343
354
  ```
344
355
 
356
+ ## Audit Trail
357
+
358
+ Rabarber supports audit trail, which provides a record of user access control activity. This feature logs the following events:
359
+
360
+ - Role assignments to users
361
+ - Role revocations from users
362
+ - Unauthorized access attempts
363
+
364
+ The logs are written to the file `log/rabarber_audit.log` unless the `audit_trail_enabled` configuration option is set to `false`.
365
+
345
366
  ## Problems?
346
367
 
347
- Encountered a bug or facing a problem?
368
+ Facing a problem or want to suggest an enhancement?
369
+
370
+ - **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.
371
+
372
+ Encountered a bug?
348
373
 
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.
374
+ - **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
375
  - **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
351
376
 
352
377
  ## Contributing
353
378
 
354
- If you want to contribute, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
379
+ Before opening an issue or creating a pull request, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
355
380
 
356
381
  ## License
357
382
 
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "singleton"
4
-
5
3
  module Rabarber
6
4
  class Configuration
7
5
  include Singleton
8
6
 
9
- attr_reader :cache_enabled, :current_user_method, :must_have_roles,
7
+ attr_reader :audit_trail_enabled, :cache_enabled, :current_user_method, :must_have_roles,
10
8
  :when_actions_missing, :when_roles_missing, :when_unauthorized
11
9
 
12
10
  def initialize
11
+ @audit_trail_enabled = default_audit_trail_enabled
13
12
  @cache_enabled = default_cache_enabled
14
13
  @current_user_method = default_current_user_method
15
14
  @must_have_roles = default_must_have_roles
@@ -18,44 +17,54 @@ module Rabarber
18
17
  @when_unauthorized = default_when_unauthorized
19
18
  end
20
19
 
20
+ def audit_trail_enabled=(value)
21
+ @audit_trail_enabled = Rabarber::Input::Types::Boolean.new(
22
+ value, Rabarber::ConfigurationError, "Configuration 'audit_trail_enabled' must be a Boolean"
23
+ ).process
24
+ end
25
+
21
26
  def cache_enabled=(value)
22
- @cache_enabled = Rabarber::Input::Types::Booleans.new(
27
+ @cache_enabled = Rabarber::Input::Types::Boolean.new(
23
28
  value, Rabarber::ConfigurationError, "Configuration 'cache_enabled' must be a Boolean"
24
29
  ).process
25
30
  end
26
31
 
27
32
  def current_user_method=(method_name)
28
- @current_user_method = Rabarber::Input::Types::Symbols.new(
33
+ @current_user_method = Rabarber::Input::Types::Symbol.new(
29
34
  method_name, Rabarber::ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
30
35
  ).process
31
36
  end
32
37
 
33
38
  def must_have_roles=(value)
34
- @must_have_roles = Rabarber::Input::Types::Booleans.new(
39
+ @must_have_roles = Rabarber::Input::Types::Boolean.new(
35
40
  value, Rabarber::ConfigurationError, "Configuration 'must_have_roles' must be a Boolean"
36
41
  ).process
37
42
  end
38
43
 
39
44
  def when_actions_missing=(callable)
40
- @when_actions_missing = Rabarber::Input::Types::Procs.new(
45
+ @when_actions_missing = Rabarber::Input::Types::Proc.new(
41
46
  callable, Rabarber::ConfigurationError, "Configuration 'when_actions_missing' must be a Proc"
42
47
  ).process
43
48
  end
44
49
 
45
50
  def when_roles_missing=(callable)
46
- @when_roles_missing = Rabarber::Input::Types::Procs.new(
51
+ @when_roles_missing = Rabarber::Input::Types::Proc.new(
47
52
  callable, Rabarber::ConfigurationError, "Configuration 'when_roles_missing' must be a Proc"
48
53
  ).process
49
54
  end
50
55
 
51
56
  def when_unauthorized=(callable)
52
- @when_unauthorized = Rabarber::Input::Types::Procs.new(
57
+ @when_unauthorized = Rabarber::Input::Types::Proc.new(
53
58
  callable, Rabarber::ConfigurationError, "Configuration 'when_unauthorized' must be a Proc"
54
59
  ).process
55
60
  end
56
61
 
57
62
  private
58
63
 
64
+ def default_audit_trail_enabled
65
+ true
66
+ end
67
+
59
68
  def default_cache_enabled
60
69
  true
61
70
  end
@@ -70,21 +79,20 @@ module Rabarber
70
79
 
71
80
  def default_when_actions_missing
72
81
  -> (missing_actions, context) {
73
- raise Rabarber::Error, "Missing actions: #{missing_actions}, context: #{context[:controller]}"
82
+ raise(Rabarber::Error, "'grant_access' method called with non-existent actions: #{missing_actions}, context: '#{context[:controller]}'")
74
83
  }
75
84
  end
76
85
 
77
86
  def default_when_roles_missing
78
87
  -> (missing_roles, context) {
79
88
  delimiter = context[:action] ? "#" : ""
80
- message = "Missing roles: #{missing_roles}, context: #{context[:controller]}#{delimiter}#{context[:action]}"
89
+ message = "'grant_access' method called with non-existent roles: #{missing_roles}, context: '#{context[:controller]}#{delimiter}#{context[:action]}'"
81
90
  Rabarber::Logger.log(:warn, message)
82
91
  }
83
92
  end
84
93
 
85
94
  def default_when_unauthorized
86
95
  -> (controller) do
87
- Rabarber::Logger.log(:warn, "Unauthorized attempt")
88
96
  if controller.request.format.html?
89
97
  controller.redirect_back fallback_location: controller.main_app.root_path
90
98
  else
@@ -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,14 +28,18 @@ 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
+ roleable = send(Rabarber::Configuration.instance.current_user_method)
32
32
 
33
- Rabarber::Configuration.instance.when_unauthorized.call(self)
34
- end
33
+ return if Rabarber::Core::Permissions.access_granted?(
34
+ roleable ? roleable.roles : [], self.class, action_name.to_sym, self
35
+ )
35
36
 
36
- def rabarber_roles
37
- user = send(Rabarber::Configuration.instance.current_user_method)
38
- user ? user.roles : []
37
+ Rabarber::Logger.audit(
38
+ :warn,
39
+ "[Unauthorized Attempt] #{Rabarber::Logger.roleable_identity(roleable, with_roles: true)} attempted to access '#{request.path}'"
40
+ )
41
+
42
+ Rabarber::Configuration.instance.when_unauthorized.call(self)
39
43
  end
40
44
  end
41
45
  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
@@ -1,11 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- module Logger
5
- module_function
4
+ class Logger
5
+ include Singleton
6
6
 
7
- def log(log_level, message)
8
- Rails.logger.tagged("Rabarber") { Rails.logger.public_send(log_level, message) }
7
+ attr_reader :rails_logger, :audit_logger
8
+
9
+ def initialize
10
+ @rails_logger = Rails.logger
11
+ @audit_logger = ::Logger.new(Rails.root.join("log/rabarber_audit.log"))
12
+ end
13
+
14
+ class << self
15
+ def log(log_level, message)
16
+ instance.rails_logger.tagged("Rabarber") { instance.rails_logger.public_send(log_level, message) }
17
+ end
18
+
19
+ def audit(log_level, message)
20
+ return unless Rabarber::Configuration.instance.audit_trail_enabled
21
+
22
+ instance.audit_logger.public_send(log_level, message)
23
+ end
24
+
25
+ def roleable_identity(roleable, with_roles:)
26
+ if roleable
27
+ model_name = roleable.model_name.human
28
+ primary_key = roleable.class.primary_key
29
+ roleable_id = roleable.public_send(primary_key)
30
+
31
+ roles = with_roles ? ", roles: #{roleable.roles}" : ""
32
+
33
+ "#{model_name} with #{primary_key}: '#{roleable_id}'#{roles}"
34
+ else
35
+ "Unauthenticated user"
36
+ end
37
+ end
9
38
  end
10
39
  end
11
40
  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
@@ -16,9 +16,7 @@ module Rabarber
16
16
  action_rules.each do |controller, controller_action_rules|
17
17
  controller_action_rules.each do |action_rule|
18
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
19
+ missing_list << Rabarber::Missing::Item.new(missing_roles, controller, action_rule.action) if missing_roles.any?
22
20
  end
23
21
  end
24
22
  end
@@ -5,9 +5,7 @@ module Rabarber
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- if defined?(@@included) && @@included != name
9
- raise Rabarber::Error, "Rabarber::HasRoles can only be included once"
10
- end
8
+ raise Rabarber::Error, "Rabarber::HasRoles can only be included once" if defined?(@@included) && @@included != name
11
9
 
12
10
  @@included = name
13
11
 
@@ -27,31 +25,47 @@ module Rabarber
27
25
  end
28
26
 
29
27
  def assign_roles(*role_names, create_new: true)
30
- roles_to_assign = process_role_names(role_names)
28
+ processed_role_names = process_role_names(role_names)
31
29
 
32
- create_new_roles(roles_to_assign) if create_new
30
+ create_new_roles(processed_role_names) if create_new
33
31
 
34
- new_roles = Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
32
+ roles_to_assign = Rabarber::Role.where(name: processed_role_names) - rabarber_roles
35
33
 
36
- if new_roles.any?
34
+ if roles_to_assign.any?
37
35
  delete_roleable_cache
38
- rabarber_roles << new_roles
36
+ rabarber_roles << roles_to_assign
37
+
38
+ Rabarber::Logger.audit(
39
+ :info,
40
+ "[Role Assignment] #{Rabarber::Logger.roleable_identity(self, with_roles: false)} has been assigned the following roles: #{roles_to_assign.pluck(:name).map(&:to_sym)}, current roles: #{roles}"
41
+ )
39
42
  end
40
43
 
41
44
  roles
42
45
  end
43
46
 
44
47
  def revoke_roles(*role_names)
45
- new_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
48
+ processed_role_names = process_role_names(role_names)
49
+ roles_to_revoke = Rabarber::Role.where(name: processed_role_names.intersection(roles))
46
50
 
47
- if rabarber_roles != new_roles
51
+ if roles_to_revoke.any?
48
52
  delete_roleable_cache
49
- self.rabarber_roles = new_roles
53
+ self.rabarber_roles -= roles_to_revoke
54
+
55
+ Rabarber::Logger.audit(
56
+ :info,
57
+ "[Role Revocation] #{Rabarber::Logger.roleable_identity(self, with_roles: false)} has been revoked from the following roles: #{roles_to_revoke.pluck(:name).map(&:to_sym)}, current roles: #{roles}"
58
+ )
50
59
  end
51
60
 
52
61
  roles
53
62
  end
54
63
 
64
+ def roleable_class
65
+ @@included.constantize
66
+ end
67
+ module_function :roleable_class
68
+
55
69
  private
56
70
 
57
71
  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
@@ -8,6 +8,11 @@ module Rabarber
8
8
  app.config.after_initialize do
9
9
  Rabarber::Missing::Actions.new.handle
10
10
  Rabarber::Missing::Roles.new.handle if Rabarber::Role.table_exists?
11
+
12
+ Rabarber::Logger.log(
13
+ :warn,
14
+ "DEPRECATION WARNING: Configurations 'when_actions_missing' and 'when_roles_missing' are deprecated and will be removed in v2.0.0"
15
+ )
11
16
  end
12
17
  end
13
18
  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.4.0"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "singleton"
4
+
3
5
  require_relative "rabarber/version"
4
6
  require_relative "rabarber/logger"
5
7
  require_relative "rabarber/configuration"
@@ -8,12 +10,13 @@ require "active_record"
8
10
  require "active_support"
9
11
 
10
12
  require_relative "rabarber/input/base"
11
- require_relative "rabarber/input/actions"
12
- require_relative "rabarber/input/dynamic_rules"
13
+ require_relative "rabarber/input/action"
14
+ require_relative "rabarber/input/dynamic_rule"
15
+ require_relative "rabarber/input/role"
13
16
  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"
17
+ require_relative "rabarber/input/types/boolean"
18
+ require_relative "rabarber/input/types/proc"
19
+ require_relative "rabarber/input/types/symbol"
17
20
 
18
21
  require_relative "rabarber/missing/base"
19
22
  require_relative "rabarber/missing/actions"
@@ -25,7 +28,8 @@ require_relative "rabarber/controllers/concerns/authorization"
25
28
  require_relative "rabarber/helpers/helpers"
26
29
  require_relative "rabarber/models/concerns/has_roles"
27
30
  require_relative "rabarber/models/role"
28
- require_relative "rabarber/permissions"
31
+
32
+ require_relative "rabarber/core/permissions"
29
33
 
30
34
  require_relative "rabarber/railtie"
31
35
 
data/rabarber.gemspec CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Rabarber::VERSION
8
8
  spec.authors = ["enjaku4", "trafium"]
9
9
  spec.email = ["rabarber_gem@icloud.com"]
10
-
11
- spec.summary = "Simple authorization library for Ruby on Rails."
10
+ spec.metadata["rubygems_mfa_required"] = "true"
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.4.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-02-20 00:00:00.000000000 Z
12
+ date: 2024-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -38,33 +38,35 @@ 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
65
66
  licenses:
66
67
  - MIT
67
- metadata: {}
68
+ metadata:
69
+ rubygems_mfa_required: 'true'
68
70
  post_install_message:
69
71
  rdoc_options: []
70
72
  require_paths:
@@ -80,8 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
82
  - !ruby/object:Gem::Version
81
83
  version: '0'
82
84
  requirements: []
83
- rubygems_version: 3.3.26
85
+ rubygems_version: 3.2.33
84
86
  signing_key:
85
87
  specification_version: 4
86
- summary: Simple authorization library for Ruby on Rails.
88
+ summary: Simple role-based authorization library for Ruby on Rails.
87
89
  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